Merge branch 'rel-4.1'
This commit is contained in:
commit
d8d36b3448
76 changed files with 6431 additions and 1842 deletions
|
|
@ -34,7 +34,7 @@ server({
|
|||
|
||||
function createWindow() {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
width: 1600,
|
||||
height: 900,
|
||||
webPreferences: {
|
||||
// preload: path.join(__dirname, 'preload.js')
|
||||
|
|
@ -53,7 +53,10 @@ function createWindow() {
|
|||
// prevent "other" urls from opening inside Electron (alerts are problematic)
|
||||
mainWindow.webContents.on('will-navigate', (event, url) => {
|
||||
// console.log('DIVERT', url);
|
||||
if (url.endsWith('/mesh') || url.endsWith('/kiri')) {
|
||||
if (url.endsWith('/kiri') || url.endsWith('/kiri/')) {
|
||||
return;
|
||||
}
|
||||
if (url.endsWith('/mesh') || url.endsWith('/mesh/')) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
|
|
|||
45
app.js
45
app.js
|
|
@ -302,6 +302,7 @@ function init(mod) {
|
|||
mod.add(fixedmap("/api/", api));
|
||||
if (debug) {
|
||||
mod.static("/mod/", "mod");
|
||||
mod.static("/mods/", "mods");
|
||||
mod.sync("/reload", () => {
|
||||
mod.reload();
|
||||
return "reload";
|
||||
|
|
@ -329,21 +330,31 @@ function init(mod) {
|
|||
mod.static("/meta/", "web/meta");
|
||||
mod.static("/kiri/", "web/kiri");
|
||||
|
||||
// load modules
|
||||
lastmod(`${dir}/mod`) && fs.readdirSync(`${dir}/mod`).forEach(dir => {
|
||||
const modpath = `mod/${dir}`;
|
||||
if (dir.charAt(0) === '.') return;
|
||||
const stats = fs.lstatSync(`${mod.dir}/${modpath}`);
|
||||
if (!(stats.isDirectory() || stats.isSymbolicLink())) return;
|
||||
const isElectronMod = util.isfile(PATH.join(mod.dir,modpath,".electron"));
|
||||
if (ENV.electron && !isElectronMod) return;
|
||||
if (!ENV.electron && isElectronMod) return;
|
||||
try {
|
||||
loadModule(mod, modpath);
|
||||
} catch (error) {
|
||||
console.log({ module: dir, error });
|
||||
}
|
||||
});
|
||||
function load_modules(root, force) {
|
||||
// load modules
|
||||
lastmod(`${dir}/${root}`) && fs.readdirSync(`${dir}/${root}`).forEach(mdir => {
|
||||
const modpath = `${root}/${mdir}`;
|
||||
if (dir.charAt(0) === '.' && !ENV.single) return;
|
||||
const stats = fs.lstatSync(`${mod.dir}/${modpath}`);
|
||||
if (!(stats.isDirectory() || stats.isSymbolicLink())) return;
|
||||
if (util.isfile(PATH.join(mod.dir,modpath,".disable"))) return;
|
||||
const isDebugMod = util.isfile(PATH.join(mod.dir,modpath,".debug"));
|
||||
const isElectronMod = util.isfile(PATH.join(mod.dir,modpath,".electron"));
|
||||
if (force || (ENV.electron && !isElectronMod)) return;
|
||||
if (force || (!ENV.electron && isElectronMod && !isDebugMod)) return;
|
||||
try {
|
||||
loadModule(mod, modpath);
|
||||
} catch (error) {
|
||||
console.log({ module: mdir, error });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// load development and 3rd party modules
|
||||
load_modules('mod');
|
||||
|
||||
// load optional local modules
|
||||
load_modules('mods');
|
||||
|
||||
// run loads injected by modules
|
||||
while (load.length) {
|
||||
|
|
@ -760,6 +771,7 @@ function getCachedFile(file, fn) {
|
|||
} else {
|
||||
logger.log({update_cache:filePath});
|
||||
cacheData = fn(filePath);
|
||||
// console.log(`NEW_CACHE_FILE: ${cachePath}`);
|
||||
fs.writeFileSync(cachePath, cacheData);
|
||||
}
|
||||
|
||||
|
|
@ -816,6 +828,9 @@ function addCorsHeaders(req, res) {
|
|||
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'X-Moto-Ajax, Content-Type');
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers['origin'] || '*');
|
||||
if (req.headers['access-control-request-private-network'] === 'true') {
|
||||
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
||||
}
|
||||
if (!crossOrigin) {
|
||||
res.setHeader("Cross-Origin-Opener-Policy", 'same-origin');
|
||||
res.setHeader("Cross-Origin-Embedder-Policy", 'require-corp');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/bash
|
||||
tag=${1:-latest}
|
||||
npm run clear-cache
|
||||
./bin/build-upload win ${tag} && \
|
||||
./bin/build-upload linux ${tag} && \
|
||||
./bin/build-upload mac ${tag}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,22 @@ const webTmp = path.join('tmp','web');
|
|||
fs.copySync("web", webTmp, { dereference: true });
|
||||
|
||||
const modTmp = path.join('tmp','mod');
|
||||
if (fs.existsSync("mod"))
|
||||
fs.copySync("mod", modTmp, { dereference: true, filter:(src,dst) => {
|
||||
const ok =
|
||||
src === 'mod' ||
|
||||
src.indexOf('mod/standalone') === 0 ||
|
||||
src.indexOf('mod/node_modules') === 0;
|
||||
// console.log(ok, src);
|
||||
return ok;
|
||||
} });
|
||||
|
||||
const modsTmp = path.join('tmp','mods');
|
||||
fs.copySync("mods", modsTmp, { dereference: true, filter:(src,dst) => {
|
||||
const ok =
|
||||
src === 'mods' ||
|
||||
src.indexOf('mods/bambu') === 0 ||
|
||||
src.indexOf('mods/electron') === 0 ||
|
||||
src.indexOf('mods/node_modules') === 0;
|
||||
return ok;
|
||||
} });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
src/ext/three-svg.js,../../node_modules/three/examples/js/loaders/SVGLoader.js
|
||||
src/ext/three.js,../../node_modules/three/build/three.min.js
|
||||
src/ext/gerber.js,../../node_modules/@tracespace/parser/umd/parser.js
|
||||
src/ext/manifold.js,../../node_modules/manifold-3d/manifold.js
|
||||
src/ext/base64.js,../../node_modules/base64-js/base64js.min.js
|
||||
src/ext/earcut.js,../../node_modules/earcut/src/earcut.js
|
||||
src/ext/three-bvh.js,../../node_modules/three-mesh-bvh/build/index.umd.cjs
|
||||
src/ext/tween.js,../../node_modules/@tweenjs/tween.js/src/Tween.js
|
||||
src/ext/three-bgu.js,../../node_modules/three/examples/js/utils/BufferGeometryUtils.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,GridBot.One
|
||||
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
|
||||
web/kiri/lang/pt-pt.js,pt.js
|
||||
web/kiri/lang/da-dk.js,da.js
|
||||
|
|
|
|||
|
0
mods/bambu/.debug
Normal file
0
mods/bambu/.debug
Normal file
0
mods/bambu/.electron
Normal file
0
mods/bambu/.electron
Normal file
1256
mods/bambu/bambu.js
Normal file
1256
mods/bambu/bambu.js
Normal file
File diff suppressed because it is too large
Load diff
27
mods/bambu/certs.js
Normal file
27
mods/bambu/certs.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Certificate authority (CA) for TLS communication with printers in LAN.
|
||||
* Country: CN
|
||||
* Organization: BBL Technologies Co., Ltd
|
||||
* Common Name: BBL CA
|
||||
*/
|
||||
exports.bblCA = `-----BEGIN CERTIFICATE-----
|
||||
MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL
|
||||
BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u
|
||||
LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw
|
||||
MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz
|
||||
IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H
|
||||
oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC
|
||||
eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk
|
||||
p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy
|
||||
/QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW
|
||||
1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP
|
||||
NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr
|
||||
3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG
|
||||
fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu
|
||||
3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA
|
||||
BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6
|
||||
itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm
|
||||
Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2
|
||||
Blbjg3obpHo9
|
||||
-----END CERTIFICATE-----`;
|
||||
181
mods/bambu/cli.js
Normal file
181
mods/bambu/cli.js
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
const [ host, pass, sn ] = process.argv.slice(2);
|
||||
const util = require('util');
|
||||
const mqtt = require("mqtt");
|
||||
const readline = require('readline');
|
||||
const { bblCA } = require("./certs");
|
||||
const reportTopic = `device/${sn}/report`;
|
||||
const requestTopic = `device/${sn}/request`;
|
||||
|
||||
console.log({ host, pass, sn, reportTopic, requestTopic });
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: '> '
|
||||
});
|
||||
|
||||
function erasePrompt() {
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
}
|
||||
|
||||
let amsmap = [];
|
||||
|
||||
rl.prompt();
|
||||
|
||||
rl.on('line', (line) => {
|
||||
line = line.trim();
|
||||
switch (line) {
|
||||
case 'quit':
|
||||
case 'exit':
|
||||
log('Exiting...');
|
||||
rl.close();
|
||||
break;
|
||||
default:
|
||||
if (line.startsWith('M') || line.startsWith('G') || line.startsWith('T')) {
|
||||
line = line.split(';').map(l => l.trimStart()).join('\n');
|
||||
sendGcode(line);
|
||||
} else if (line.startsWith('ams ')) {
|
||||
amsmap = JSON.parse(line.substring(4));
|
||||
log({ amsmap });
|
||||
} else if (line.startsWith('print ')) {
|
||||
printStart(line.slice(6));
|
||||
} else if (line === 'stop') {
|
||||
printStop();
|
||||
} else if (line === 'pause') {
|
||||
printPause();
|
||||
} else if (line === 'resume') {
|
||||
printResume();
|
||||
} else if (line.startsWith('{')) {
|
||||
let cmd = eval('(' + line + ')');
|
||||
log('sending', cmd);
|
||||
sendRequest(cmd);
|
||||
} else if (line) {
|
||||
log('invalid command:', line);
|
||||
}
|
||||
rl.prompt();
|
||||
break;
|
||||
}
|
||||
}).on('close', () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
const options = {
|
||||
protocol: 'mqtts',
|
||||
host: host,
|
||||
port: 8883,
|
||||
username: 'bblp',
|
||||
password: pass,
|
||||
ca: bblCA,
|
||||
servername: sn
|
||||
};
|
||||
|
||||
function log() {
|
||||
erasePrompt();
|
||||
console.log(
|
||||
[...arguments]
|
||||
.map(v => util.inspect(v, {
|
||||
maxArrayLength: null,
|
||||
breakLength: this.break,
|
||||
colors: true,
|
||||
compact: true,
|
||||
sorted: true,
|
||||
depth: Infinity
|
||||
}))
|
||||
.join(this.join)
|
||||
);
|
||||
rl.prompt(true);
|
||||
}
|
||||
|
||||
function sendRequest(obj) {
|
||||
client.publish(requestTopic, JSON.stringify(obj));
|
||||
}
|
||||
|
||||
function sendGcode(gcode) {
|
||||
sendRequest({
|
||||
print: {
|
||||
command: "gcode_line",
|
||||
param: gcode,
|
||||
sequence_id: "0"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printStart(file) {
|
||||
log('print', { file });
|
||||
sendRequest({
|
||||
print: {
|
||||
command: "project_file",
|
||||
sequence_id: "0",
|
||||
url: `file:///sdcard/${file}`,
|
||||
param: "Metadata/plate_1.gcode",
|
||||
subtask_id: "0",
|
||||
use_ams: amsmap.length ? true : false,
|
||||
timelapse: false,
|
||||
flow_cali: false,
|
||||
bed_leveling: false,
|
||||
layer_inspect: false,
|
||||
vibration_cali: false,
|
||||
ams_mapping: amsmap || []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printStop() {
|
||||
sendRequest({
|
||||
print: {
|
||||
command: "stop",
|
||||
sequence_id: "0"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printPause() {
|
||||
sendRequest({
|
||||
print: {
|
||||
command: "pause",
|
||||
sequence_id: "0"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function printResume() {
|
||||
sendRequest({
|
||||
print: {
|
||||
command: "resume",
|
||||
sequence_id: "0"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const client = mqtt.connect(options);
|
||||
|
||||
client.on("connect", () => {
|
||||
log("mqtt connected");
|
||||
client.subscribe(reportTopic, (err) => {
|
||||
if (err) {
|
||||
log('mqtt subscribe error', err);
|
||||
} else {
|
||||
log('mqtt subscribed');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
client.on("error", (error) => {
|
||||
log("mqtt error", error);
|
||||
});
|
||||
|
||||
client.on("message", (topic, message) => {
|
||||
log(JSON.parse(message.toString()));
|
||||
});
|
||||
|
||||
client.on("close", () => {
|
||||
log("mqtt disconnect");
|
||||
process.exit();
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
log('SIGINT. Running cleanup...');
|
||||
client.end();
|
||||
process.exit(0);
|
||||
});
|
||||
102
mods/bambu/filament.js
Normal file
102
mods/bambu/filament.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
self.kiri.load(api => {
|
||||
|
||||
let list = [
|
||||
["GFB00", "Bambu ABS"],
|
||||
["GFB50", "Bambu ABS-GF"],
|
||||
["GFB01", "Bambu ASA"],
|
||||
["GFB02", "Bambu ASA-Aero"],
|
||||
["GFB51", "Bambu ASA-CF"],
|
||||
["GFN03", "Bambu PA-CF"],
|
||||
["GFN05", "Bambu PA6-CF"],
|
||||
["GFN08", "Bambu PA6-GF"],
|
||||
["GFN04", "Bambu PAHT-CF"],
|
||||
["GFC01", "Bambu PC FR"],
|
||||
["GFC00", "Bambu PC"],
|
||||
["GFT01", "Bambu PET-CF"],
|
||||
["GFG00", "Bambu PETG Basic"],
|
||||
["GFG02", "Bambu PETG HF"],
|
||||
["GFG01", "Bambu PETG Translucent"],
|
||||
["GFG50", "Bambu PETG-CF"],
|
||||
["GFA11", "Bambu PLA Aero"],
|
||||
["GFA00", "Bambu PLA Basic"],
|
||||
["GFA13", "Bambu PLA Dynamic"],
|
||||
["GFA15", "Bambu PLA Galaxy"],
|
||||
["GFA12", "Bambu PLA Glow"],
|
||||
["GFA07", "Bambu PLA Marble"],
|
||||
["GFA01", "Bambu PLA Matte"],
|
||||
["GFA02", "Bambu PLA Metal"],
|
||||
["GFA05", "Bambu PLA Silk"],
|
||||
["GFA06", "Bambu PLA Silk+"],
|
||||
["GFA08", "Bambu PLA Sparkle"],
|
||||
["GFA09", "Bambu PLA Tough"],
|
||||
["GFA16", "Bambu PLA Wood"],
|
||||
["GFA50", "Bambu PLA-CF"],
|
||||
["GFN06", "Bambu PPA-CF"],
|
||||
["GFT02", "Bambu PPS-CF"],
|
||||
["GFS04", "Bambu PVA"],
|
||||
["GFS03", "Bambu Support For PA/PET"],
|
||||
["GFS02", "Bambu Support For PLA"],
|
||||
["GFS05", "Bambu Support For PLA/PETG"],
|
||||
["GFS01", "Bambu Support G"],
|
||||
["GFS00", "Bambu Support W"],
|
||||
["GFS06", "Bambu Support for ABS"],
|
||||
["GFU00", "Bambu TPU 95A HF"],
|
||||
["GFU01", "Bambu TPU 95A"],
|
||||
["GFU02", "Bambu TPU for AMS"],
|
||||
["GFL52", "Fiberon PA12-CF"],
|
||||
["GFL50", "Fiberon PA6-CF"],
|
||||
["GFL51", "Fiberon PA6-GF"],
|
||||
["GFL53", "Fiberon PA612-CF"],
|
||||
["GFL54", "Fiberon PET-CF"],
|
||||
["GFL06", "Fiberon PETG-ESD"],
|
||||
["GFL55", "Fiberon PETG-rCF"],
|
||||
["GFB99", "Generic ABS"],
|
||||
["GFB98", "Generic ASA"],
|
||||
["GFS97", "Generic BVOH"],
|
||||
["GFR99", "Generic EVA"],
|
||||
["GFS98", "Generic HIPS"],
|
||||
["GFN99", "Generic PA"],
|
||||
["GFN98", "Generic PA-CF"],
|
||||
["GFC99", "Generic PC"],
|
||||
["GFG97", "Generic PCTG"],
|
||||
["GFP99", "Generic PE"],
|
||||
["GFP98", "Generic PE-CF"],
|
||||
["GFG96", "Generic PETG HF"],
|
||||
["GFG99", "Generic PETG"],
|
||||
["GFG98", "Generic PETG-CF"],
|
||||
["GFR98", "Generic PHA"],
|
||||
["GFL95", "Generic PLA High Speed"],
|
||||
["GFL96", "Generic PLA Silk"],
|
||||
["GFL99", "Generic PLA"],
|
||||
["GFL98", "Generic PLA-CF"],
|
||||
["GFP97", "Generic PP"],
|
||||
["GFP96", "Generic PP-CF"],
|
||||
["GFP95", "Generic PP-GF"],
|
||||
["GFN97", "Generic PPA-CF"],
|
||||
["GFN96", "Generic PPA-GF"],
|
||||
["GFT97", "Generic PPS"],
|
||||
["GFT98", "Generic PPS-CF"],
|
||||
["GFS99", "Generic PVA"],
|
||||
["GFU98", "Generic TPU for AMS"],
|
||||
["GFU99", "Generic TPU"],
|
||||
["GFL05", "Overture Matte PLA"],
|
||||
["GFL04", "Overture PLA"],
|
||||
["GFB60", "PolyLite ABS"],
|
||||
["GFB61", "PolyLite ASA"],
|
||||
["GFG60", "PolyLite PETG"],
|
||||
["GFL00", "PolyLite PLA"],
|
||||
["GFL01", "PolyTerra PLA"],
|
||||
["GFL03", "eSUN PLA+"]
|
||||
];
|
||||
|
||||
let map = {};
|
||||
|
||||
for (let row of list) {
|
||||
map[row[0]] = row[1];
|
||||
}
|
||||
|
||||
api.bambu.filament = {
|
||||
list, map
|
||||
};
|
||||
|
||||
});
|
||||
97
mods/bambu/frames.js
Normal file
97
mods/bambu/frames.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* this utility takes a BBL printer host and LAN code
|
||||
* and stores the resulting jpeg stream into frame files
|
||||
*/
|
||||
|
||||
const { bblCA } = require('./certs');
|
||||
const EventEmitter = require('events');
|
||||
const tls = require('tls');
|
||||
const debug = false;
|
||||
|
||||
class FrameStream extends EventEmitter {
|
||||
#remoteSocket;
|
||||
|
||||
constructor(host, code, serial) {
|
||||
super();
|
||||
|
||||
const abuf = new ArrayBuffer(80);
|
||||
const view = new DataView(abuf);
|
||||
const encoder = new TextEncoder();
|
||||
const userBytes = encoder.encode('bblp');
|
||||
const codeBytes = encoder.encode(code);
|
||||
const useCA = false;
|
||||
|
||||
view.setInt32(0, 0x0040, true);
|
||||
view.setInt32(4, 0x3000, true);
|
||||
new Uint8Array(abuf, 0x10, userBytes.length).set(userBytes);
|
||||
new Uint8Array(abuf, 0x30, codeBytes.length).set(codeBytes);
|
||||
|
||||
let frame;
|
||||
const remoteSocket = this.#remoteSocket = tls.connect(Object.assign({}, {
|
||||
host,
|
||||
port: 6000
|
||||
}, useCA ? {
|
||||
ca: bblCA,
|
||||
servername: serial
|
||||
} : {
|
||||
rejectUnauthorized: false,
|
||||
checkServerIdentity: () => {}
|
||||
}), () => {
|
||||
// send authentication to start jpeg frame stream
|
||||
remoteSocket.write(Buffer.from(abuf));
|
||||
this.emit('connect', host);
|
||||
});
|
||||
debug && console.log('start frames', serial);
|
||||
|
||||
remoteSocket.on('data', (data) => {
|
||||
if (data.length === 16) {
|
||||
if (frame) {
|
||||
this.emit('frame', frame);
|
||||
debug && console.log('frame', serial);
|
||||
frame = undefined;
|
||||
}
|
||||
} else {
|
||||
frame = frame ? Buffer.concat([frame, data]) : data;
|
||||
}
|
||||
});
|
||||
|
||||
remoteSocket.on('error', (error) => {
|
||||
remoteSocket.destroy();
|
||||
this.emit('error', error);
|
||||
});
|
||||
|
||||
remoteSocket.on('close', () => {
|
||||
debug && console.log('close frames', serial);
|
||||
this.emit('close');
|
||||
})
|
||||
}
|
||||
|
||||
end() {
|
||||
this.#remoteSocket.end();
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
const fs = require('fs');
|
||||
const args = process.argv.slice(2);
|
||||
const [host, code, prefix] = args;
|
||||
|
||||
if (!(host && code)) {
|
||||
console.log('usage: frames [host] [code] (file-prefix)');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('frames extraction from', host);
|
||||
|
||||
let ind = 0;
|
||||
new FrameStream(host, code)
|
||||
.on('connect', host => {
|
||||
console.log('connected to', host);
|
||||
})
|
||||
.on('frame', jpeg => {
|
||||
fs.writeFileSync(`${prefix || "frame"}-${(++ind).toString().padStart(5, 0)}.jpg`, jpeg);
|
||||
console.log('received frame', ind);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { FrameStream };
|
||||
442
mods/bambu/init.js
Normal file
442
mods/bambu/init.js
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
const { Client } = require('@gridspace/basic-ftp');
|
||||
const { Readable } = require('stream');
|
||||
const { FrameStream } = require('./frames');
|
||||
const { bblCA } = require('./certs');
|
||||
|
||||
module.exports = async (server) => {
|
||||
|
||||
const { api, env, util } = server;
|
||||
const confdir = util.confdir();
|
||||
const mqtt = require("mqtt");
|
||||
const mcache = {};
|
||||
const wsopen = [];
|
||||
const found = {};
|
||||
const video = {};
|
||||
const useCA = false;
|
||||
const debug = false;
|
||||
|
||||
class MQTT {
|
||||
#timer;
|
||||
#timer2;
|
||||
#client;
|
||||
#serial;
|
||||
#frames;
|
||||
#topic_report;
|
||||
#topic_request;
|
||||
#options = Object.assign({}, {
|
||||
protocol: 'mqtts',
|
||||
port: 8883,
|
||||
username: 'bblp',
|
||||
}, useCA ? {
|
||||
ca: bblCA
|
||||
} : {
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
constructor(host, code, serial, onready, onerror, onmessage) {
|
||||
this.#options.host = host;
|
||||
this.#options.password = code;
|
||||
this.#options.servername = serial;
|
||||
this.#serial = serial;
|
||||
let client = this.#client = mqtt.connect(this.#options);
|
||||
|
||||
client.on("connect", () => {
|
||||
let report = this.#topic_report = `device/${serial}/report`;
|
||||
let request = this.#topic_request = `device/${serial}/request`;
|
||||
// util.log({ report, request });
|
||||
client.subscribe(report, (err) => {
|
||||
debug && util.log('mqtt sub', this.#serial, err || "ok");
|
||||
onready(this);
|
||||
this.keepalive();
|
||||
// this.keepconn();
|
||||
});
|
||||
});
|
||||
|
||||
client.on("message", (topic, message) => {
|
||||
message = JSON.parse(message.toString());
|
||||
if (onmessage) {
|
||||
onmessage(message);
|
||||
} else {
|
||||
debug && util.log('mqtt_recv', this.#serial, message);
|
||||
}
|
||||
});
|
||||
|
||||
client.on("error", error => onerror(error));
|
||||
}
|
||||
|
||||
set_frames(bool) {
|
||||
video[this.#serial] = bool;
|
||||
if (this.#frames && !bool) {
|
||||
this.#frames.end();
|
||||
this.#frames = undefined;
|
||||
} else if (!this.#frames && bool) {
|
||||
let { host, password } = this.#options;
|
||||
this.#frames = new FrameStream(host, password, this.#serial)
|
||||
.on("frame", jpg => {
|
||||
wsend({ serial: this.#serial, frame: jpg.toString('base64') });
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
keepalive() {
|
||||
clearTimeout(this.#timer);
|
||||
this.#timer = setTimeout(() => {
|
||||
util.log('keepalive expired', this.#serial);
|
||||
this.end();
|
||||
}, 300000);
|
||||
}
|
||||
|
||||
keepconn() {
|
||||
clearTimeout(this.#timer2);
|
||||
this.#timer2 = setTimeout(() => { this.keepconn() }, 120000);
|
||||
if_mqtt(this.#serial, {
|
||||
print: {
|
||||
sequence_id: "0",
|
||||
command: "push_status",
|
||||
msg: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async send(msg) {
|
||||
if (this.#client) {
|
||||
debug && util.log('mqtt send', this.#serial, msg);
|
||||
this.#client.publish(this.#topic_request, JSON.stringify(msg));
|
||||
this.keepalive();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.#client) {
|
||||
util.log('mqtt end', this.#serial);
|
||||
this.#client.end();
|
||||
this.#client = undefined;
|
||||
this.set_frames(false);
|
||||
}
|
||||
this.#topic_report = undefined;
|
||||
this.#topic_request = undefined;
|
||||
delete mcache[this.#serial];
|
||||
}
|
||||
}
|
||||
|
||||
function if_mqtt(serial, msg) {
|
||||
mcache[serial]?.send(msg);
|
||||
}
|
||||
|
||||
function get_mqtt(host, code, serial, onmsg, onconn) {
|
||||
const fns = {};
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
Object.assign(fns, { resolve, reject });
|
||||
});
|
||||
|
||||
let mqtt = mcache[serial];
|
||||
if (mqtt) {
|
||||
fns.resolve(mqtt);
|
||||
} else {
|
||||
mqtt = new MQTT(host, code, serial, obj => {
|
||||
mcache[serial] = obj;
|
||||
fns.resolve(obj);
|
||||
if (onconn) {
|
||||
onconn(mqtt);
|
||||
}
|
||||
mcache[serial]?.set_frames(video[serial]);
|
||||
}, error => fns.reject(error), onmsg);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function ftp_open(args = {}) {
|
||||
const client = new Client();
|
||||
const port = parseInt(args.port || 990);
|
||||
const host = args.host || "localhost";
|
||||
const user = args.user || "bblp";
|
||||
const password = args.password || args.code || '';
|
||||
client.ftp.verbose = debug;
|
||||
try {
|
||||
await client.access({
|
||||
port,
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
secure: "implicit",
|
||||
secureOptions: useCA ? {
|
||||
// this appears to break some
|
||||
// operations like file deletion
|
||||
ca: bblCA,
|
||||
servername: args.serial
|
||||
} : {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
util.log({ ftp_error: error });
|
||||
throw error;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
async function ftp_send(args = {}) {
|
||||
const client = await ftp_open(args);
|
||||
const filename = args.filename || "test.3mf";
|
||||
const data = args.data || undefined;
|
||||
try {
|
||||
const readableStream = new Readable();
|
||||
readableStream._read = () => {};
|
||||
readableStream.push(data);
|
||||
readableStream.push(null);
|
||||
await client.uploadFrom(readableStream, filename);
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function ftp_list(args = {}) {
|
||||
const client = await ftp_open(args);
|
||||
const list = [];
|
||||
try {
|
||||
let files = await client.list();
|
||||
files.forEach(file => file.root = "");
|
||||
list.push(...files);
|
||||
} catch (e) { }
|
||||
try {
|
||||
let files = await client.list("/cache");
|
||||
files.forEach(file => file.root = "cache/");
|
||||
list.push(...files);
|
||||
} catch (e) { }
|
||||
client.close();
|
||||
return list;
|
||||
}
|
||||
|
||||
async function ftp_delete(args = {}) {
|
||||
const client = await ftp_open(args);
|
||||
try {
|
||||
await client.remove(args.path);
|
||||
} catch (error) {
|
||||
util.log({ ftp_delete_error: error });
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
|
||||
function file_print(opts = {}) {
|
||||
const { host, code, serial, filename, amsmap } = opts;
|
||||
const cmd = {
|
||||
print: {
|
||||
command: "project_file",
|
||||
url: `file:///sdcard/${filename}`,
|
||||
param: "Metadata/plate_1.gcode",
|
||||
subtask_id: "0",
|
||||
use_ams: amsmap ? true : false,
|
||||
timelapse: false,
|
||||
flow_cali: false,
|
||||
bed_leveling: false,
|
||||
layer_inspect: false,
|
||||
vibration_cali: false
|
||||
}
|
||||
};
|
||||
if (amsmap && amsmap !== 'auto') {
|
||||
cmd.print.ams_mapping = amsmap.split(',').map(v => parseInt(v));
|
||||
}
|
||||
util.log({ file_print: cmd });
|
||||
get_mqtt(host, code, serial, message => {
|
||||
debug && util.log('mqtt_recv', message);
|
||||
wsend({ serial, message });
|
||||
})
|
||||
.then(mqtt => mqtt.send(cmd))
|
||||
.catch(err => {
|
||||
util.log({ mqtt_err: err });
|
||||
});
|
||||
}
|
||||
|
||||
function decode_post(req, res, next) {
|
||||
if (req.method === 'POST') {
|
||||
let chunks = [];
|
||||
req
|
||||
.on('data', data => chunks.push(data) )
|
||||
.on('end', () => {
|
||||
req.app.post = Buffer.concat(chunks);
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
// insert scripts before all others in kiri client
|
||||
server.inject("kiri", "bambu.js");
|
||||
server.inject("kiri", "filament.js");
|
||||
|
||||
function o2s(obj) {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
||||
function wsend(msg) {
|
||||
wsopen.forEach(ws => ws.send(JSON.stringify(msg)));
|
||||
}
|
||||
|
||||
if (!(env.debug || env.electron)) {
|
||||
util.log('not a valid context for bambu');
|
||||
return;
|
||||
}
|
||||
|
||||
// start SSDP listener for local Bambu printer broadcasts
|
||||
{
|
||||
const dgram = require("dgram");
|
||||
const SSDP_ADDRESS = "239.255.255.250";
|
||||
const SSDP_PORT = 1990;
|
||||
const socket = dgram.createSocket("udp4");
|
||||
|
||||
socket.on("message", msg => {
|
||||
msg = msg.toString();
|
||||
if (msg.indexOf('.bambu.com') > 0) {
|
||||
let rec = {};
|
||||
map = msg.split('\n')
|
||||
.filter(l => l.indexOf(': ') > 0)
|
||||
.map(l => l.trim().replace('.bambu.com','').split(': '));
|
||||
map.forEach(line => rec[line[0]] = line[1]);
|
||||
// console.log({ ssdp: rec, map })
|
||||
if (rec.DevName && rec.Location) {
|
||||
let nurec = {
|
||||
host: rec.Location,
|
||||
name: rec.DevName,
|
||||
type: rec.DevModel,
|
||||
firm: rec.DevVersion,
|
||||
srno: rec.USN
|
||||
}
|
||||
if (!found[rec.DevName]) {
|
||||
found[rec.DevName] = nurec;
|
||||
util.log(`found Bambu ${nurec.name} ${nurec.srno} @ ${nurec.host}`);
|
||||
}
|
||||
wsend({ found });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.bind(SSDP_PORT, () => {
|
||||
socket.addMembership(SSDP_ADDRESS);
|
||||
});
|
||||
}
|
||||
|
||||
api.bambu_send = (req, res, next) => {
|
||||
const { app, url, headers } = req;
|
||||
const { host } = headers;
|
||||
const { query } = app;
|
||||
server.handler.addCORS(req, res);
|
||||
decode_post(req, res, async () => {
|
||||
res.setHeader("Content-Type", "application/octet-stream");
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, private');
|
||||
const data = req.app.post;
|
||||
const { host, code, filename, serial, ams, start } = query;
|
||||
ftp_send({ host, code, filename, data, serial })
|
||||
.then(() => {
|
||||
if (serial && start ==='true') {
|
||||
file_print({ host, code, serial, filename, amsmap: ams });
|
||||
}
|
||||
res.end(o2s({ sent: true }));
|
||||
})
|
||||
.catch(error => {
|
||||
util.log({ ftp_send_error: error });
|
||||
res.end(o2s({ sent: false, error }));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
server.ws.register("/bambu", function(ws, req) {
|
||||
wsopen.push(ws);
|
||||
debug && util.log('ws open', req.url, wsopen.length);
|
||||
wsend({ found });
|
||||
ws.on('message', msg => {
|
||||
msg = JSON.parse(msg);
|
||||
let { cmd, host, code, serial, path, amsmap, direct, frames } = msg;
|
||||
switch (cmd) {
|
||||
case "monitor":
|
||||
get_mqtt(host, code, serial, message => {
|
||||
// util.log({ mqtt_msg: serial });
|
||||
wsend({ serial, message });
|
||||
}, mqtt => {
|
||||
// on open only
|
||||
}).then(mqtt => {
|
||||
// util.log({ mqtt_mon: mqtt });
|
||||
// request all printer state info
|
||||
mqtt.send({
|
||||
pushing: {
|
||||
sequence_id: "0",
|
||||
command: "pushall"
|
||||
}
|
||||
});
|
||||
// request system info
|
||||
mqtt.send({
|
||||
info: {
|
||||
command: "get_version"
|
||||
}
|
||||
});
|
||||
// announce all current monitor hosts
|
||||
wsend({ monitoring: Object.keys(mcache) });
|
||||
}).catch(error => {
|
||||
util.log({ mqtt_err: error });
|
||||
wsend({ serial, error: error.message || error.toString() });
|
||||
});
|
||||
break;
|
||||
case "files":
|
||||
ftp_list({ host, code, serial }).then(files => {
|
||||
debug && util.log({ ftp_files: files.length });
|
||||
// console.log(JSON.stringify(files,undefined,4));
|
||||
files = files
|
||||
.filter(file => file.name.toLowerCase().endsWith(".3mf"))
|
||||
.map(file => {
|
||||
return {
|
||||
root: file.root,
|
||||
name: file.name,
|
||||
path: file.root + file.name,
|
||||
size: file.size,
|
||||
date: file.rawModifiedAt
|
||||
};
|
||||
});
|
||||
wsend({ serial, files });
|
||||
}).catch(error => {
|
||||
util.log({ ftp_error: error });
|
||||
wsend({ serial, error: error.message || error.toString() });
|
||||
});
|
||||
break;
|
||||
case "file-delete":
|
||||
ftp_delete({ host, code, path, serial }).then(() => {
|
||||
util.log({ ftp_delete: path });
|
||||
wsend({ serial, deleted: path });
|
||||
});
|
||||
break;
|
||||
case "file-print":
|
||||
file_print({ host, code, serial, filename: path, amsmap });
|
||||
break;
|
||||
case "pause":
|
||||
if_mqtt(serial, { print: { command: "pause", sequence_id: "0" } });
|
||||
break;
|
||||
case "resume":
|
||||
if_mqtt(serial, { print: { command: "resume", sequence_id: "0" } });
|
||||
break;
|
||||
case "cancel":
|
||||
if_mqtt(serial, { print: { command: "stop", sequence_id: "0", param: "" } });
|
||||
break;
|
||||
case "direct":
|
||||
if_mqtt(serial, direct);
|
||||
break;
|
||||
case "frames":
|
||||
debug && util.log('request frames', serial, frames);
|
||||
mcache[serial]?.set_frames(frames);
|
||||
video[serial] = frames;
|
||||
break;
|
||||
case "keepalive":
|
||||
// util.log({ keepalive: serial });
|
||||
mcache[serial]?.keepalive();
|
||||
break;
|
||||
}
|
||||
});
|
||||
ws.on('close', () => {
|
||||
let io = wsopen.indexOf(ws);
|
||||
if (io >= 0) wsopen.splice(io, 1);
|
||||
debug && util.log('ws close', wsopen.length);
|
||||
});
|
||||
});
|
||||
};
|
||||
262
mods/bambu/proxy.js
Normal file
262
mods/bambu/proxy.js
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/**
|
||||
* Utility to proxy local MQTT requests to a remote Bambu printer
|
||||
* and intercept / display communications.
|
||||
*
|
||||
* Bambu's network plugin will not connect to this process unless
|
||||
* you use the private key and certs extracted from Bambu Connect
|
||||
* following the instructions here:
|
||||
*
|
||||
* https://wiki.rossmanngroup.com/wiki/Reverse_engineering_Bambu_Connect
|
||||
*
|
||||
* The private key is clearly delineated. Put the contents into the
|
||||
* server-key.pem file. But you need to concatenate all of the certs
|
||||
* after the private key into the server-cert.pem
|
||||
*/
|
||||
|
||||
let util = require('util');
|
||||
const { bblCA } = require('./certs');
|
||||
let args = process.argv.slice(2);
|
||||
|
||||
if (args.length !== 5) {
|
||||
console.log([
|
||||
'usage: proxy [local] [name] [host] [code] [serial-no]',
|
||||
'where:',
|
||||
' local = local ip to broadcast (this host)',
|
||||
' name = name of printer to appear in slicer',
|
||||
' host = host name or IP address of proxied printer',
|
||||
' code = LAN mode code of proxied printer',
|
||||
' serial = proxied printer serial #'
|
||||
].join('\n'));
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
function log() {
|
||||
console.log(
|
||||
new Date().toISOString().replace(/[T.]/g, ' ').split(' ').slice(1,2).join(' '),
|
||||
[...arguments]
|
||||
.map(v => util.inspect(v, {
|
||||
maxArrayLength: null,
|
||||
breakLength: this.break,
|
||||
colors: true,
|
||||
compact: true,
|
||||
depth: Infinity
|
||||
}))
|
||||
.join(' ')
|
||||
);
|
||||
}
|
||||
|
||||
const dgram = require("dgram");
|
||||
const SSDP_ADDRESS = "239.255.255.250";
|
||||
const SSDP_PORT = 1900;
|
||||
const socket = dgram.createSocket("udp4");
|
||||
|
||||
socket.on("error", error => log({ error }));
|
||||
socket.bind(1900, () => {
|
||||
socket.addMembership(SSDP_ADDRESS);
|
||||
});
|
||||
|
||||
const [local, name, host, code, serial] = args;
|
||||
|
||||
console.log([
|
||||
`Broadcasting Bambu Printer Proxy`,
|
||||
`Name: ${name}`,
|
||||
`Host: ${host}`,
|
||||
`Serial: ${serial}`,
|
||||
].join('\n'));
|
||||
|
||||
const ssdpMessage = `
|
||||
NOTIFY * HTTP/1.1
|
||||
HOST: ${SSDP_ADDRESS}:${SSDP_PORT}
|
||||
Server: UPnP/1.0
|
||||
Location: ${local}
|
||||
NT: urn:bambulab-com:device:3dprinter:1
|
||||
USN: ${serial}
|
||||
Cache-Control: max-age=1800
|
||||
DevModel.bambu.com: C11
|
||||
DevName.bambu.com: ${name}
|
||||
DevSignal.bambu.com: -66
|
||||
DevConnect.bambu.com: lan
|
||||
DevBind.bambu.com: free
|
||||
Devseclink.bambu.com: secure
|
||||
DevVersion.bambu.com: 01.07.00.00
|
||||
DevCap.bambu.com: 1`.trim().split('\n').join("\r\n") + "\r\n\r\n";
|
||||
|
||||
console.log({ ssdpMessage });
|
||||
|
||||
// start broadcaster
|
||||
setInterval(() => {
|
||||
socket.send(
|
||||
ssdpMessage, 0,
|
||||
ssdpMessage.length, 2021,
|
||||
SSDP_ADDRESS,
|
||||
(err, data) => err && console.error("SSDP broadcast error:", err, data)
|
||||
);
|
||||
}, 1000);
|
||||
|
||||
// const { execSync } = require('child_process');
|
||||
const aedes = require('aedes')();
|
||||
const tls = require('tls');
|
||||
const fs = require('fs');
|
||||
const mqtt = require('mqtt');
|
||||
const path = require('path');
|
||||
|
||||
const CERT_DIR = './certs';
|
||||
const CERT_KEY_PATH = path.join(CERT_DIR, 'server-key.pem');
|
||||
const CERT_PATH = path.join(CERT_DIR, 'server-cert.pem');
|
||||
const CA_CERT_PATH = path.join(CERT_DIR, 'ca-cert.pem');
|
||||
|
||||
if (!fs.existsSync(CERT_DIR)) {
|
||||
fs.mkdirSync(CERT_DIR);
|
||||
}
|
||||
|
||||
// Generate self-signed certificate if it does not exist (local cli testing only)
|
||||
// if (!fs.existsSync(CERT_KEY_PATH) || !fs.existsSync(CERT_PATH)) {
|
||||
// console.log('Generating self-signed certificate...');
|
||||
// execSync(`openssl req -x509 -newkey rsa:4096 -keyout ${CERT_KEY_PATH} -out ${CERT_PATH} -days 365 -nodes -subj "/CN=localhost"`);
|
||||
// }
|
||||
if (!fs.existsSync(CERT_KEY_PATH) || !fs.existsSync(CERT_PATH)) {
|
||||
console.log('missing required key and cert');
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync(CERT_KEY_PATH),
|
||||
cert: fs.readFileSync(CERT_PATH),
|
||||
ca: fs.existsSync(CA_CERT_PATH) ? fs.readFileSync(CA_CERT_PATH) : undefined
|
||||
};
|
||||
|
||||
const remoteMqttOptions = {
|
||||
host,
|
||||
port: 8883,
|
||||
username: 'bblp',
|
||||
password: code,
|
||||
protocol: 'mqtts',
|
||||
ca: bblCA,
|
||||
servername: serial
|
||||
};
|
||||
|
||||
// Start local MQTTS broker
|
||||
const server = tls.createServer(options, (socket) => {
|
||||
log(`MQTTS server connection`, socket.remoteAddress, socket.remotePort);
|
||||
aedes.handle(socket);
|
||||
});
|
||||
|
||||
server.listen(8883, () => {
|
||||
log('MQTTS server running on port 8883');
|
||||
});
|
||||
|
||||
// Connect to remote MQTTS broker
|
||||
const remoteClient = mqtt.connect(remoteMqttOptions);
|
||||
|
||||
remoteClient.on('connect', () => {
|
||||
log('Connected to remote MQTTS broker');
|
||||
});
|
||||
|
||||
aedes.preConnect = (client, packet, callback) => {
|
||||
let { id, version } = client;
|
||||
let { cmd, username, password, clientId, protocolId } = packet;
|
||||
log(`mqtts preconnect`, {
|
||||
id,
|
||||
version,
|
||||
cmd,
|
||||
username,
|
||||
password: password ? password.toString() : undefined,
|
||||
clientId,
|
||||
protocolId
|
||||
});
|
||||
callback(null, true);
|
||||
};
|
||||
|
||||
aedes.authenticate = (client, username, password, callback) => {
|
||||
log(`mqtts auth`, { username, password: password.toString() });
|
||||
const isValid = true;
|
||||
callback(null, isValid);
|
||||
};
|
||||
|
||||
// Proxy messages to remote broker
|
||||
aedes.on('publish', (packet, client) => {
|
||||
if (client && packet.topic !== 'aedes/keepalive') {
|
||||
remoteClient.publish(packet.topic, packet.payload, { qos: packet.qos, retain: packet.retain });
|
||||
try {
|
||||
let { topic, payload } = packet;
|
||||
let json = JSON.parse(payload.toString().replace('\x00',''));
|
||||
log('send', topic, json);
|
||||
} catch (err) {
|
||||
log({ err, packet, payload: packet.payload.toString() });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to remote messages and forward to local clients
|
||||
remoteClient.on('message', (topic, payload) => {
|
||||
aedes.publish({ topic, payload });
|
||||
try {
|
||||
let json = JSON.parse(payload.toString());
|
||||
log('recv', topic, json);
|
||||
} catch (err) {
|
||||
log({ topic, payload: payload.toString() });
|
||||
}
|
||||
});
|
||||
|
||||
// Sync subscriptions
|
||||
aedes.on('subscribe', (subscriptions, client) => {
|
||||
subscriptions.forEach(sub => {
|
||||
log({ subscribe: sub.topic });
|
||||
remoteClient.subscribe(sub.topic);
|
||||
});
|
||||
});
|
||||
|
||||
aedes.on('unsubscribe', (subscriptions, client) => {
|
||||
subscriptions.forEach(sub => {
|
||||
log({ unsubscribe: sub.topic });
|
||||
remoteClient.unsubscribe(sub);
|
||||
});
|
||||
});
|
||||
|
||||
// pipe the camera feed, too
|
||||
const camera = tls.createServer(options, (clientSocket) => {
|
||||
log('Camera Connected', { address: clientSocket.remoteAddress });
|
||||
|
||||
const hexer = require('hexer');
|
||||
|
||||
const remoteSocket = tls.connect({
|
||||
host,
|
||||
port: 6000,
|
||||
ca: bblCA,
|
||||
servername: serial
|
||||
}, () => {
|
||||
// clientSocket.pipe(remoteSocket).pipe(clientSocket);
|
||||
});
|
||||
|
||||
clientSocket.on('data', (data) => {
|
||||
remoteSocket.write(data);
|
||||
// console.log({ client: data, type: typeof data });
|
||||
// console.log('-- cam client --', data.length);
|
||||
// console.log(hexer(data));
|
||||
});
|
||||
|
||||
remoteSocket.on('data', (data) => {
|
||||
clientSocket.write(data);
|
||||
// console.log({ remote: data, type: typeof data });
|
||||
// console.log('-- cam remote --', data.length);
|
||||
// console.log(hexer(data));
|
||||
});
|
||||
|
||||
clientSocket.on('close', () => {
|
||||
remoteSocket.end();
|
||||
});
|
||||
|
||||
remoteSocket.on('error', (err) => {
|
||||
console.error('Remote connection error:', err.message);
|
||||
clientSocket.destroy();
|
||||
});
|
||||
|
||||
clientSocket.on('error', (err) => {
|
||||
console.error('Client connection error:', err.message);
|
||||
remoteSocket.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
camera.listen(6000, () => {
|
||||
console.log(`TLS Proxy Server listening on port 6000`);
|
||||
});
|
||||
90
mods/bambu/ssdp.js
Normal file
90
mods/bambu/ssdp.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* Utility to make a Bambu printer appear on the local subnet
|
||||
* so that the Bambu Network Plugin can find it. The intended
|
||||
* use case is where a printer is on another subnet but reachable
|
||||
* directly (routable) and Bambu Studio / Orca Slicer can't find
|
||||
* it because SSDP broadcasts do not cross subnets.
|
||||
*
|
||||
* When this process is run from the command line, it should appear
|
||||
* under the slicer Devices tab when "+" is selected.
|
||||
*/
|
||||
|
||||
let args = process.argv.slice(2);
|
||||
|
||||
if (args.length % 3 !== 0) {
|
||||
console.log([
|
||||
'usage: ssdp [name] [host] [serial-no]',
|
||||
'where:',
|
||||
' name = name of printer to appear in slicer',
|
||||
' host = host name or IP address of printer',
|
||||
' serial = printer serial #'
|
||||
].join('\n'));
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
const dgram = require("dgram");
|
||||
|
||||
// SSDP parameters
|
||||
const SSDP_ADDRESS = "239.255.255.250";
|
||||
const SSDP_PORT = 1900;
|
||||
|
||||
// Create a UDP socket
|
||||
const socket = dgram.createSocket("udp4");
|
||||
|
||||
socket.on("error", error => console.log({ error }));
|
||||
|
||||
// Bind the socket and join the multicast group
|
||||
socket.bind(1900, () => {
|
||||
socket.addMembership(SSDP_ADDRESS); // Join the SSDP multicast group
|
||||
});
|
||||
|
||||
while (args.length) {
|
||||
const [name, host, serial] = args;
|
||||
args = args.slice(3);
|
||||
|
||||
console.log([
|
||||
`Broadcasting Bambu Printer`,
|
||||
`Name: ${name}`,
|
||||
`Host: ${host}`,
|
||||
`Serial: ${serial}`,
|
||||
].join('\n'));
|
||||
|
||||
// SSDP discovery message
|
||||
const ssdpMessage = `
|
||||
NOTIFY * HTTP/1.1
|
||||
HOST: ${SSDP_ADDRESS}:${SSDP_PORT}
|
||||
Server: UPnP/1.0
|
||||
Location: ${host}
|
||||
NT: urn:bambulab-com:device:3dprinter:1
|
||||
USN: ${serial}
|
||||
Cache-Control: max-age=1800
|
||||
DevModel.bambu.com: C11
|
||||
DevName.bambu.com: ${name}
|
||||
DevSignal.bambu.com: -66
|
||||
DevConnect.bambu.com: lan
|
||||
DevBind.bambu.com: free
|
||||
Devseclink.bambu.com: secure
|
||||
DevVersion.bambu.com: 01.07.00.00
|
||||
DevCap.bambu.com: 1`
|
||||
.trim().split('\n').join("\r\n") + "\r\n\r\n";
|
||||
|
||||
// Send the SSDP broadcast
|
||||
function send(socket, addr, port) {
|
||||
socket.send(
|
||||
ssdpMessage,
|
||||
0,
|
||||
ssdpMessage.length,
|
||||
port,
|
||||
addr,
|
||||
(err) => {
|
||||
if (err) {
|
||||
console.error("Error sending SSDP broadcast:", err);
|
||||
} else {
|
||||
// console.log(`>>>>>>>>>>>>>>>>> ${addr} : ${port}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setInterval(() => send(socket, '239.255.255.250', 2021), 1000);
|
||||
}
|
||||
0
mods/electron/.electron
Normal file
0
mods/electron/.electron
Normal file
13
mods/electron/electron.js
Normal file
13
mods/electron/electron.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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';
|
||||
});
|
||||
api.stats.set('kiri', self.kiri.version + 'e');
|
||||
});
|
||||
if (self.mesh && self.mesh.api) {
|
||||
self.mesh.api.electron = {};
|
||||
}
|
||||
9
mods/electron/init.js
Normal file
9
mods/electron/init.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* for electron standalone build support
|
||||
*/
|
||||
module.exports = function(server) {
|
||||
// insert script before all others in kiri client
|
||||
server.inject("kiri", "electron.js");
|
||||
// insert scripts into mesh client
|
||||
server.inject("mesh", "electron.js");
|
||||
};
|
||||
16
mods/package.json
Normal file
16
mods/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "grid-apps-mods",
|
||||
"version": "4.1.0",
|
||||
"description": "grid.space app modules",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gridspace/apps.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gridspace/basic-ftp": "^5.0.5",
|
||||
"aedes": "^0.51.3",
|
||||
"hexer": "^1.5.0",
|
||||
"mqtt": "^5.10.3"
|
||||
}
|
||||
}
|
||||
49
package.json
49
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "grid-apps",
|
||||
"version": "4.0.31",
|
||||
"description": "grid.space 3d slice & modeling tools",
|
||||
"version": "4.1.0",
|
||||
"description": "grid.space 3d slicing & modeling tools",
|
||||
"author": "Stewart Allen <sa@grid.space>",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
|
|
@ -11,12 +11,23 @@
|
|||
},
|
||||
"keywords": [
|
||||
"grid.space",
|
||||
"mesh:tool",
|
||||
"kiri:moto",
|
||||
"kirimoto",
|
||||
"kiri",
|
||||
"3d",
|
||||
"mesh",
|
||||
"editor",
|
||||
"3D",
|
||||
"FDM",
|
||||
"CAM",
|
||||
"CNC",
|
||||
"mSLA",
|
||||
"laser",
|
||||
"waterjet",
|
||||
"wire edm",
|
||||
"wire-edm",
|
||||
"dragknife",
|
||||
"drag knife",
|
||||
"gcode",
|
||||
"slicer"
|
||||
],
|
||||
|
|
@ -58,6 +69,9 @@
|
|||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"setup": "npm i; cd mods && npm i",
|
||||
"dev": "gs-app-server --debug",
|
||||
"prod": "gs-app-server",
|
||||
"start": "npm run prebuild && electron .",
|
||||
"start-dev": "npm run prebuild && electron . --devel",
|
||||
"start-dbg": "npm run prebuild && electron . --debugg",
|
||||
|
|
@ -72,12 +86,14 @@
|
|||
"build-mac-intel": "npm run build -- --mac --x64",
|
||||
"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"
|
||||
},
|
||||
"main": "app-el.js",
|
||||
"build": {
|
||||
"npmRebuild": false,
|
||||
"appId": "space.grid.kiri",
|
||||
"productName": "KiriMoto",
|
||||
"artifactName": "KiriMoto-${os}-${arch}.${ext}",
|
||||
|
|
@ -103,10 +119,30 @@
|
|||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mod/node_modules",
|
||||
"to": "mod/node_modules",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mods",
|
||||
"to": "mods",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "tmp/mods/node_modules",
|
||||
"to": "mods/node_modules",
|
||||
"filter": [
|
||||
"**/*"
|
||||
]
|
||||
},
|
||||
"bin/*",
|
||||
"conf/**/*",
|
||||
"data/**/*",
|
||||
"dist/**/*",
|
||||
"data/cache/**/*",
|
||||
"app-el.js",
|
||||
"app.js",
|
||||
"package.json"
|
||||
|
|
@ -142,8 +178,7 @@
|
|||
"linux": {
|
||||
"icon": "bin/GS.png",
|
||||
"target": [
|
||||
"AppImage",
|
||||
"zip"
|
||||
"AppImage"
|
||||
]
|
||||
},
|
||||
"afterSign": "bin/electron-notarize.js"
|
||||
|
|
|
|||
117
readme.md
117
readme.md
|
|
@ -1,10 +1,33 @@
|
|||
## Grid.Space Web Applications
|
||||
# Grid.Space Applications
|
||||
|
||||
[](https://grid.space/kiri/)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
# Community & Documentation
|
||||
|
||||
[Discord](https://discord.com/invite/suyCCgr) -- Live Chat
|
||||
[Forums](https://forum.grid.space/) -- Long Form and Archival Discussion
|
||||
[BlueSky](https://bsky.app/profile/grid.space) -- Like the Good 'Ol Days
|
||||
[YouTube](https://www.youtube.com/c/gridspace) -- Content when I have time
|
||||
[Documentation](https://docs.grid.space/) -- Could really use help with this
|
||||
|
||||
|
||||
# Free and Open Source
|
||||
|
||||
Kiri:Moto and Mesh:Tool are completely open source and free for use without restriction. Over 12 years in development, this passion project has grown well beyond its original scope. It has consumed most of my free time for many years. Please consider donating to support continued development GitHub sponsorship or PayPal.
|
||||
|
||||
[](https://github.com/sponsors/GridSpace)
|
||||
[](https://paypal.me/gridspace3d?locale.x=en_US)
|
||||
|
||||
|
||||
# HTML5 Web Apps (Installable)
|
||||
|
||||
[`Grid.Space`](https://grid.space) hosts [several live versions](https://grid.space/choose) of this code
|
||||
|
||||
|
|
@ -12,7 +35,8 @@
|
|||
|
||||
[`Mesh:Tool`](https://grid.space/mesh) is a browser-based mesh repair and editing tool
|
||||
|
||||
## Electron Builds (Desktop Binaries)
|
||||
|
||||
# Electron Builds (Desktop Binaries)
|
||||
|
||||
https://github.com/GridSpace/grid-apps/releases/
|
||||
|
||||
|
|
@ -28,90 +52,43 @@ chmod 755 KiriMoto-linux-x86_64.AppImage
|
|||
|
||||
The Windows and Mac binaries are not signed, so you will need to jump through a few safety hoops to get them to open the first time.
|
||||
|
||||
## Primary Documentation
|
||||
|
||||
https://docs.grid.space/projects/kiri-moto
|
||||
|
||||
https://docs.grid.space/projects/mesh-tool
|
||||
|
||||
## Development Activity
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Community Engagement
|
||||
|
||||
[Discord](https://discord.com/invite/suyCCgr)
|
||||
| [YouTube](https://www.youtube.com/c/gridspace)
|
||||
| [Twitter](https://twitter.com/grid_space_3d)
|
||||
|
||||
[](https://discord.com/channels/688863523207774209/688863523211968535)
|
||||

|
||||
[](https://paypal.me/gridspace3d?locale.x=en_US)
|
||||

|
||||
|
||||
# Linux / Mac Developers
|
||||
|
||||
## Testing Locally (with Docker)
|
||||
|
||||
```
|
||||
git clone git@github.com:GridSpace/grid-apps.git
|
||||
cd grid-apps
|
||||
npm run setup
|
||||
docker-compose -f src/dock/compose.yml up
|
||||
```
|
||||
|
||||
## Testing Locally (with Electron)
|
||||
|
||||
```
|
||||
git clone git@github.com:GridSpace/grid-apps.git
|
||||
cd grid-apps
|
||||
npm run setup
|
||||
npm run start
|
||||
```
|
||||
|
||||
## Testing Locally (with NodeJS)
|
||||
|
||||
```
|
||||
git clone git@github.com:GridSpace/grid-apps.git
|
||||
cd grid-apps
|
||||
npm i
|
||||
npm install -g @gridspace/app-server
|
||||
gs-app-server --debug
|
||||
npm run setup
|
||||
npm run dev
|
||||
```
|
||||
|
||||
to start a local instance of the apps. then use a browser to open
|
||||
[localhost:8080/kiri](http://localhost:8080/kiri)
|
||||
Then open a browser to [localhost:8080/kiri](http://localhost:8080/kiri)
|
||||
|
||||
if installing the app-server fails or gives you permissions errors, then your node installation (on linux/mac) is installed as another user (like root). try instead:
|
||||
|
||||
```
|
||||
sudo npm install -g @gridspace/app-server
|
||||
```
|
||||
|
||||
Alternatively, if you are using a packaged version of npm that ships with
|
||||
a Linux distribution, but still want to install in your home directory, you
|
||||
can use
|
||||
|
||||
```
|
||||
npm config set prefix ~/.local
|
||||
```
|
||||
|
||||
If gs-app-server is not found, then perhaps ~/.local/bin is not in
|
||||
your path. You can either add it to your path, or you can run:
|
||||
|
||||
```
|
||||
~/.local/bin/gs-app-server --debug
|
||||
```
|
||||
|
||||
You can now access your environment of grid-apps by going to
|
||||
[localhost:8080/kiri](http://127.0.0.1:8080/kiri)
|
||||
|
||||
## Windows Developers
|
||||
# Windows Developers
|
||||
|
||||
this git repo requires symbolic link support. on Windows, this means you have to clone the repo in a command shell with Administrator privileges.
|
||||
|
||||
## Other Start Options
|
||||
|
||||
```
|
||||
gs-app-server
|
||||
```
|
||||
serves code as obfuscated, compressed bundles. this is the mode used to run on a public
|
||||
web site.
|
||||
|
||||
requires node.js 12+
|
||||
|
||||
## Javascript Slicing APIs
|
||||
# Javascript Slicing APIs
|
||||
|
||||
A script include that injects a web worker into the page that will asynchronously perform any of Kiri’s slicing and gcode generation functions. And a frame messaging API for controlling Kiri:Moto inside an IFrame.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
"add/array",
|
||||
"add/class",
|
||||
"ext/three",
|
||||
"ext/three-bgu",
|
||||
"add/three",
|
||||
"ext/clip2",
|
||||
"ext/earcut",
|
||||
|
|
|
|||
72
src/ext/md5.js
Normal file
72
src/ext/md5.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// SOURCE: https://stackoverflow.com/questions/1655769/fastest-md5-implementation-in-javascript
|
||||
gapp.register("ext.md5", [], (root, exports) => {
|
||||
|
||||
exports({ hash });
|
||||
|
||||
function hash(e) {
|
||||
function h(a, b) {
|
||||
var c, d, e, f, g;
|
||||
e = a & 2147483648;
|
||||
f = b & 2147483648;
|
||||
c = a & 1073741824;
|
||||
d = b & 1073741824;
|
||||
g = (a & 1073741823) + (b & 1073741823);
|
||||
return c & d ? g ^ 2147483648 ^ e ^ f : c | d ? g & 1073741824 ? g ^ 3221225472 ^ e ^ f : g ^ 1073741824 ^ e ^ f : g ^ e ^ f
|
||||
}
|
||||
|
||||
function k(a, b, c, d, e, f, g) {
|
||||
a = h(a, h(h(b & c | ~b & d, e), g));
|
||||
return h(a << f | a >>> 32 - f, b)
|
||||
}
|
||||
|
||||
function l(a, b, c, d, e, f, g) {
|
||||
a = h(a, h(h(b & d | c & ~d, e), g));
|
||||
return h(a << f | a >>> 32 - f, b)
|
||||
}
|
||||
|
||||
function m(a, b, d, c, e, f, g) {
|
||||
a = h(a, h(h(b ^ d ^ c, e), g));
|
||||
return h(a << f | a >>> 32 - f, b)
|
||||
}
|
||||
|
||||
function n(a, b, d, c, e, f, g) {
|
||||
a = h(a, h(h(d ^ (b | ~c), e), g));
|
||||
return h(a << f | a >>> 32 - f, b)
|
||||
}
|
||||
|
||||
function p(a) {
|
||||
var b = "",
|
||||
d = "",
|
||||
c;
|
||||
for (c = 0; 3 >= c; c++) d = a >>> 8 * c & 255, d = "0" + d.toString(16), b += d.substr(d.length - 2, 2);
|
||||
return b
|
||||
}
|
||||
var f = [],
|
||||
q, r, s, t, a, b, c, d;
|
||||
e = function(a) {
|
||||
a = a.replace(/\r\n/g, "\n");
|
||||
for (var b = "", d = 0; d < a.length; d++) {
|
||||
var c = a.charCodeAt(d);
|
||||
128 > c ? b += String.fromCharCode(c) : (127 < c && 2048 > c ? b += String.fromCharCode(c >> 6 | 192) : (b += String.fromCharCode(c >> 12 | 224), b += String.fromCharCode(c >> 6 & 63 | 128)), b += String.fromCharCode(c & 63 | 128))
|
||||
}
|
||||
return b
|
||||
}(e);
|
||||
f = function(b) {
|
||||
var a, c = b.length;
|
||||
a = c + 8;
|
||||
for (var d = 16 * ((a - a % 64) / 64 + 1), e = Array(d - 1), f = 0, g = 0; g < c;) a = (g - g % 4) / 4, f = g % 4 * 8, e[a] |= b.charCodeAt(g) << f, g++;
|
||||
a = (g - g % 4) / 4;
|
||||
e[a] |= 128 << g % 4 * 8;
|
||||
e[d - 2] = c << 3;
|
||||
e[d - 1] = c >>> 29;
|
||||
return e
|
||||
}(e);
|
||||
a = 1732584193;
|
||||
b = 4023233417;
|
||||
c = 2562383102;
|
||||
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()
|
||||
};
|
||||
|
||||
});
|
||||
|
|
@ -160,12 +160,24 @@ function inRange(value, min, max) {
|
|||
}
|
||||
|
||||
function round(v, zeros) {
|
||||
if (typeof v === 'object') {
|
||||
for (let [key,val] of Object.entries(v)) {
|
||||
if (typeof val === 'number') {
|
||||
v[key] = round(val, zeros);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
const prec = zeros !== undefined ? zeros : round_decimal_precision;
|
||||
if (prec === 0) return v | 0;
|
||||
let pow = Math.pow(10, prec);
|
||||
return Math.round(v * pow) / pow;
|
||||
}
|
||||
|
||||
function clamp(val, low, hi) {
|
||||
return Math.max(low, Math.min(hi, val));
|
||||
}
|
||||
|
||||
/**
|
||||
* used by {@link Polygon.trace} and {@link Polygon.intersect}
|
||||
*/
|
||||
|
|
@ -590,6 +602,7 @@ base.util = {
|
|||
sqr,
|
||||
lerp,
|
||||
time,
|
||||
clamp,
|
||||
comma,
|
||||
round,
|
||||
area2,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ function slice(off, res, val) {
|
|||
// or the requested resolution or tip values have changed
|
||||
let now = Date.now();
|
||||
if (res !== lastRes || val !== lastVal || now - lastSlice > 20000) {
|
||||
// console.log({clear_cache: now});
|
||||
cache = {};
|
||||
}
|
||||
lastVal = val;
|
||||
|
|
|
|||
|
|
@ -956,6 +956,7 @@ class Polygon {
|
|||
ln = ar.length,
|
||||
i = 0;
|
||||
while (i < ln) ar[i++].z = z;
|
||||
this.z = z;
|
||||
if (this.inner) this.inner.forEach(c => c.setZ(z));
|
||||
return this;
|
||||
}
|
||||
|
|
@ -964,7 +965,7 @@ class Polygon {
|
|||
* @returns {number} z value of first point
|
||||
*/
|
||||
getZ(i) {
|
||||
return this.z !== undefined ? this.z : this.points[i || 0].z;
|
||||
return this.z !== undefined ? this.z : this.points[i || 0]?.z || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2036,7 +2037,7 @@ class Polygon {
|
|||
tree = new PolyTree(),
|
||||
sp1 = this.toClipper(),
|
||||
sp2 = poly.toClipper(),
|
||||
minarea = min >= 0 ? min : 0.1;
|
||||
minarea = min ?? 0.1;
|
||||
|
||||
clip.AddPaths(sp1, PathSubject, true);
|
||||
clip.AddPaths(sp2, PathClip, true);
|
||||
|
|
|
|||
|
|
@ -345,10 +345,10 @@ function flatten(polys, to, crush) {
|
|||
* @returns {Polygon[]} out
|
||||
*/
|
||||
function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
||||
let min = minArea || 0.1,
|
||||
let min = numOrDefault(minArea, 0.1),
|
||||
out = [];
|
||||
|
||||
function filter(from, to = []) {
|
||||
function filter(from, to = []) {
|
||||
from.forEach(function(poly) {
|
||||
if (poly.area() >= min) {
|
||||
to.push(poly);
|
||||
|
|
@ -431,7 +431,7 @@ function subtract(setA, setB, outA, outB, z, minArea, opt = {}) {
|
|||
let lpre = length(polys);
|
||||
|
||||
if (opt.wasm && geo.wasm) {
|
||||
let min = minarea || 0.01;
|
||||
let min = minarea ?? 0.01;
|
||||
// let deepLength = polys.map(p => p.deepLength).reduce((a,v) => a+v);
|
||||
// if (deepLength < 15000)
|
||||
try {
|
||||
|
|
@ -632,7 +632,7 @@ function offset(polys, dist, opts = {}) {
|
|||
let coff = new ClipperOffset(opts.miter, opts.arc),
|
||||
tree = new PolyTree();
|
||||
|
||||
// setup offset
|
||||
// setup offset
|
||||
for (let poly of polys) {
|
||||
// convert to clipper format
|
||||
poly = poly.toClipper();
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@
|
|||
"gcodePre": [
|
||||
"'UNITS:MM",
|
||||
"'",
|
||||
" \t'Set program to absolute coordinate mode",
|
||||
"CN, 90",
|
||||
"&Tool =1 'Tool number to change to",
|
||||
"CN, 90 'Set program to absolute coordinate mode",
|
||||
"&Tool =1 'Tool number to change to",
|
||||
"C9 'Change tool",
|
||||
"TR,4000 'Set spindle RPM",
|
||||
"TR,4000 'Set spindle RPM",
|
||||
"C6 'Spindle on",
|
||||
"PAUSE 2",
|
||||
"'"
|
||||
|
|
@ -24,9 +23,8 @@
|
|||
"C7",
|
||||
"END",
|
||||
"'",
|
||||
"'",
|
||||
"UNIT_ERROR:",
|
||||
"CN, 91 'Run file explaining unit error",
|
||||
"CN, 91 'Run file explaining unit error",
|
||||
"END"
|
||||
],
|
||||
"gcodeDwell": [
|
||||
|
|
|
|||
824
src/kiri-dev/fdm/Bambu.A1
Normal file
824
src/kiri-dev/fdm/Bambu.A1
Normal file
|
|
@ -0,0 +1,824 @@
|
|||
{
|
||||
"mode": "FDM",
|
||||
"internal": 0,
|
||||
"bedHeight": 2.5,
|
||||
"bedWidth": 256,
|
||||
"bedDepth": 256,
|
||||
"bedRound": false,
|
||||
"maxHeight": 256,
|
||||
"originCenter": false,
|
||||
"gcodeFan": [
|
||||
"M106 P1 S{fan_speed}"
|
||||
],
|
||||
"gcodeTrack": [
|
||||
"M73 P{progress}"
|
||||
],
|
||||
"gcodeLayer": [
|
||||
"; ===== CHANGE_LAYER ==============================",
|
||||
"; layer num/total_layer_count: {layer}/{layers}",
|
||||
"M622.1 S1 ; for prev firware, default turned on",
|
||||
"M1002 judge_flag timelapse_record_flag",
|
||||
"M622 J1",
|
||||
"M971 S11 C10 O0 ; timelapse without wipe tower",
|
||||
"M623",
|
||||
"M73 L{layer}",
|
||||
"M991 S0 P{layer-1} ; notify layer change",
|
||||
";; IF { layer == 0 }",
|
||||
"M204 S500 ; reduce first layer acceleration",
|
||||
";; ELSE",
|
||||
"M204 S5000 ; faster acceleration",
|
||||
";; END"
|
||||
],
|
||||
"gcodePre": [
|
||||
"; total layer number: {layers}",
|
||||
";; PREAMBLE",
|
||||
";; DEFINE BAMBU-AMS 0,1,2,3",
|
||||
"; ===== HEADER ====================================",
|
||||
"M73 P0 R24 ; Initial progress and time remaining",
|
||||
"M201 X12000 Y12000 Z1500 E5000 ; Set max acceleration",
|
||||
"M203 X500 Y500 Z30 E30 ; Set max feed rates",
|
||||
"M204 P12000 R5000 T12000 ; Set acceleration",
|
||||
"M205 X9.00 Y9.00 Z3.00 E2.50 ; Set advanced settings",
|
||||
"M106 S0 ; Set part fan speed to 0",
|
||||
"M106 P2 S0 ; Turn off aux fan",
|
||||
";===== Machine: A1 ================================",
|
||||
"G392 S0 ; Disable clog detection",
|
||||
"M9833.2 ; Custom command (unknown function)",
|
||||
";===== Start Heating ==============================",
|
||||
"M1002 gcode_claim_action : 2",
|
||||
"M1002 set_filament_type:PLA ; Set filament type to PLA",
|
||||
"M104 S140 ; Set nozzle temperature",
|
||||
"M140 S65 ; Set bed temperature",
|
||||
";===== Startup Sound ==============================",
|
||||
"M17 ; Enable steppers",
|
||||
"M400 S1 ; Wait for movements to complete",
|
||||
"M1006 S1 ; Start printer sound",
|
||||
"M1006 A0 B10 L100 C37 D10 M60 E37 F10 N60",
|
||||
"M1006 A0 B10 L100 C41 D10 M60 E41 F10 N60",
|
||||
"M1006 A0 B10 L100 C44 D10 M60 E44 F10 N60",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N60",
|
||||
"M1006 A43 B10 L100 C46 D10 M70 E39 F10 N80",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N80",
|
||||
"M1006 A0 B10 L100 C43 D10 M60 E39 F10 N80",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N80",
|
||||
"M1006 A0 B10 L100 C41 D10 M80 E41 F10 N80",
|
||||
"M1006 A0 B10 L100 C44 D10 M80 E44 F10 N80",
|
||||
"M1006 A0 B10 L100 C49 D10 M80 E49 F10 N80",
|
||||
"M1006 A0 B10 L100 C0 D10 M80 E0 F10 N80",
|
||||
"M1006 A44 B10 L100 C48 D10 M60 E39 F10 N80",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N80",
|
||||
"M1006 A0 B10 L100 C44 D10 M80 E39 F10 N80",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N80",
|
||||
"M1006 A43 B10 L100 C46 D10 M60 E39 F10 N80",
|
||||
"M1006 W ; Play audio",
|
||||
"M18 ; Disable steppers",
|
||||
";===== Avoid End Stop =============================",
|
||||
"G91 ; Set relative positioning",
|
||||
"G380 S2 Z40 F1200 ; Move Z up to 40mm",
|
||||
"G380 S3 Z-15 F1200 ; Move Z down to -15mm",
|
||||
"G90 ; Set absolute positioning",
|
||||
";===== Reset Machine Status =======================",
|
||||
"M204 S6000 ; Set acceleration to 6000 mm/s^2",
|
||||
"M630 S0 P0 ; Custom command (unknown function)",
|
||||
"G91 ; Set relative positioning",
|
||||
"M17 Z0.3 ; Lower Z motor current",
|
||||
"G90 ; Set absolute positioning",
|
||||
"M17 X0.65 Y1.2 Z0.6 ; Reset motor currents",
|
||||
"M960 S5 P1 ; Turn on logo lamp",
|
||||
"G90 ; Set absolute positioning",
|
||||
"M220 S100 ; Reset feedrate",
|
||||
"M221 S100 ; Reset flowrate",
|
||||
"M73.2 R1.0 ; Reset estimated time remaining",
|
||||
";===== Enable Noise Reduction =====================",
|
||||
"M982.2 S1 ; Turn on noise reduction",
|
||||
";===== Home XY & Prepare Print ====================",
|
||||
"M1002 gcode_claim_action : 13",
|
||||
"G28 X ; Home X-axis",
|
||||
"G91 ; Set relative positioning",
|
||||
"G1 Z5 F1200 ; Move Z up by 5mm",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G0 X128 F30000 ; Move X to 128mm",
|
||||
"G0 Y254 F3000 ; Move Y to 254mm",
|
||||
"G91 ; Set relative positioning",
|
||||
"G1 Z-5 F1200 ; Move Z down by 5mm",
|
||||
"M109 S25 H140 ; Set tool preheat temperature",
|
||||
"M17 E0.3 ; Set extruder motor current",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"G1 E10 F1200 ; Extrude 10mm",
|
||||
"G1 E-0.5 F30 ; Retract 0.5mm",
|
||||
"M17 D ; Restore motor current",
|
||||
";===== Home Z with Low Precision ==================",
|
||||
"G28 Z P0 T140 ; Home Z with low precision",
|
||||
"M104 S220 ; Set nozzle temperature",
|
||||
";===== Build Plate Detection ======================",
|
||||
"M1002 judge_flag build_plate_detect_flag",
|
||||
"M622 S1",
|
||||
" G39.4 ; Custom build plate detection command",
|
||||
" G90 ; Set absolute positioning",
|
||||
" G1 Z5 F1200 ; Move Z up by 5mm",
|
||||
"M623",
|
||||
";===== Prepare Print Temperature & Material =======",
|
||||
"M1002 gcode_claim_action : 24",
|
||||
"M400 ; Wait for all movements to complete",
|
||||
"M211 X0 Y0 Z0 ; Turn off soft endstops",
|
||||
"M975 S1 ; Enable tool vibration suppression",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 X-28.5 F30000 ; Move X-axis",
|
||||
"G1 X-48.2 F3000 ; Move further on X",
|
||||
"M620 M ; Enable material remap",
|
||||
"M620 S0A ; Switch material if AMS exists",
|
||||
" M1002 gcode_claim_action : 4",
|
||||
" M400 ; Wait for heating",
|
||||
" M1002 set_filament_type:UNKNOWN ; Set filament type as unknown",
|
||||
" M109 S220 ; Set and wait hotend temperature",
|
||||
" M104 S250 ; Set nozzle purge temp",
|
||||
" M400 ; Wait for heating",
|
||||
" T0 ; Select Tool 0",
|
||||
" G1 X-48.2 F3000 ; Move X again",
|
||||
" M400 ; Wait for completion",
|
||||
" M620.1 E F523.843 T240 ; Extrude for purge sequence",
|
||||
" M109 S250 ; Wait for nozzle flush temp",
|
||||
" M106 P1 S0 ; Turn off nozzle cooling fan",
|
||||
" G92 E0 ; Reset extruder position",
|
||||
" G1 E50 F200 ; Extrude 50mm to clear nozzle",
|
||||
" M400 ; Wait for all movements to complete",
|
||||
" M1002 set_filament_type:PLA ; Confirm filament type",
|
||||
"M621 S0A ; Confirm material switch",
|
||||
"M109 S240 H300 ; Set nozzle temp",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E50 F200 ; Extrude 50mm slowly to prevent clog",
|
||||
"M400 ; Wait for completion",
|
||||
"M106 P1 S178 ; Set nozzle cooling fan speed",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E5 F200 ; Extrude 5mm slowly",
|
||||
"M104 S220 ; Lower nozzle temperature",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E-0.5 F300 ; Retract filament slightly",
|
||||
";===== Wipe and Shake Nozzle ======================",
|
||||
"G1 X-28.5 F30000 ; Move X-axis",
|
||||
"M73 P1 R23 ; Update print progress",
|
||||
"G1 X-48.2 F3000 ; Move back on X-axis",
|
||||
"M73 P2 R23 ; Update print progress",
|
||||
"G1 X-28.5 F30000 ; Wipe nozzle",
|
||||
"G1 X-48.2 F3000 ; Move back on X",
|
||||
"G1 X-28.5 F30000 ; Wipe nozzle",
|
||||
"G1 X-48.2 F3000 ; Move back on X",
|
||||
"M400 ; Wait for all movements to complete",
|
||||
"M106 P1 S0 ; Turn off nozzle cooling fan",
|
||||
";===== Auto Extrude Calibration Start =============",
|
||||
"M975 S1 ; Enable extruder calibration",
|
||||
"G90 ; Set absolute positioning",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"T1000 ; Select tool 1000",
|
||||
"G1 X-48.2 Y0 Z10 F10000 ; Move to calibration start position",
|
||||
"M400 ; Wait for all moves to complete",
|
||||
"M1002 set_filament_type:UNKNOWN ; Set filament type to unknown",
|
||||
"M412 S1 ; Enable filament runout detection",
|
||||
"M400 P10 ; Wait for 10ms",
|
||||
"M620.3 W1 ; Enable filament tangle detection",
|
||||
"M400 S2 ; Wait for 2ms",
|
||||
"M1002 set_filament_type:PLA ; Set filament type to PLA",
|
||||
"M1002 judge_flag extrude_cali_flag ; Check extruder calibration flag",
|
||||
"M622 J1",
|
||||
" M1002 gcode_claim_action : 8",
|
||||
" M109 S220 ; Set hotend temperature",
|
||||
" G1 E10 F377.08 ; Extrude 10mm at 377.08 mm/min",
|
||||
" M983 F6.28466 A0.3 H0.4 ; Perform dynamic extrusion compensation",
|
||||
" M106 P1 S255 ; Set nozzle cooling fan to max speed",
|
||||
" M400 S5 ; Wait for 5 seconds",
|
||||
" G1 X-28.5 F18000 ; Move X for wipe",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" G1 X-28.5 F18000 ; Wipe motion",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" M73 P3 R23 ; Update progress",
|
||||
" G1 X-28.5 F12000 ; Wipe and shake",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" M400 ; Wait for all movements to complete",
|
||||
" M106 P1 S0 ; Turn off nozzle cooling fan",
|
||||
" M1002 judge_last_extrude_cali_success",
|
||||
" M622 J0",
|
||||
" M983 F6.28466 A0.3 H0.4 ; Perform dynamic extrusion compensation",
|
||||
" M106 P1 S255 ; Set nozzle cooling fan to max speed",
|
||||
" M400 S5 ; Wait for 5 seconds",
|
||||
" G1 X-28.5 F18000 ; Move X for wipe",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" G1 X-28.5 F18000 ; Wipe motion",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" G1 X-28.5 F12000 ; Wipe and shake",
|
||||
" M400 ; Wait for all movements to complete",
|
||||
" M106 P1 S0 ; Turn off nozzle cooling fan",
|
||||
" M623 ; End of calibration routine",
|
||||
" M73 P4 R23 ; Update progress",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" M400 ; Wait for all movements to complete",
|
||||
" M984 A0.1 E1 S1 F6.28466 H0.4 ; Apply extruder calibration parameters",
|
||||
" M106 P1 S178 ; Set nozzle cooling fan to moderate speed",
|
||||
" M400 S7 ; Wait for 7 seconds",
|
||||
" G1 X-28.5 F18000 ; Move X for wipe",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" G1 X-28.5 F18000 ; Wipe motion",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" G1 X-28.5 F12000 ; Wipe and shake",
|
||||
" G1 X-48.2 F3000 ; Move X back",
|
||||
" M400 ; Wait for all movements to complete",
|
||||
" M106 P1 S0 ; Turn off nozzle cooling fan",
|
||||
"M623 ; End of extruder calibration process",
|
||||
"M104 S170 ; Prepare to wipe nozzle",
|
||||
"M106 S255 ; Turn on fan",
|
||||
";===== Mech Mode Fast Check Start =================",
|
||||
"M1002 gcode_claim_action : 3",
|
||||
"G1 X128 Y128 F20000 ; Move to check position",
|
||||
"G1 Z5 F1200 ; Raise Z-axis",
|
||||
"M400 P200 ; Wait for movements to complete",
|
||||
"M970.3 Q1 A5 K0 O3 ; Execute fast check routine",
|
||||
"M974 Q1 S2 P0 ; Confirm fast check step",
|
||||
"M970.2 Q1 K1 W58 Z0.1 ; Additional fast check",
|
||||
"M974 S2 ; Confirm step",
|
||||
"G1 X128 Y128 F20000 ; Repeat move to check position",
|
||||
"G1 Z5 F1200 ; Raise Z-axis",
|
||||
"M400 P200 ; Wait for movements to complete",
|
||||
"M970.3 Q0 A10 K0 O1 ; Execute another fast check routine",
|
||||
"M974 Q0 S2 P0 ; Confirm step",
|
||||
"M970.2 Q0 K1 W78 Z0.1 ; Another additional check",
|
||||
"M974 S2 ; Confirm step",
|
||||
"M975 S1 ; Confirm vibration suppression",
|
||||
"G1 F30000 ; Set movement speed",
|
||||
"G1 X0 Y5 ; Move to next step",
|
||||
"G28 X ; Re-home XY",
|
||||
"G1 Z4 F1200 ; Lower Z-axis",
|
||||
";===== Wipe Nozzle ================================",
|
||||
"M1002 gcode_claim_action : 14",
|
||||
"M975 S1 ; Enable wipe mode",
|
||||
"M106 S255 ; Turn on fan (G28 turns off fan)",
|
||||
"M211 S ; Push soft endstop status",
|
||||
"M211 X0 Y0 Z0 ; Turn off Z-axis endstop",
|
||||
";===== Remove Waste by Touching Start =============",
|
||||
"M104 S170 ; Set nozzle temp",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"G1 E-1 F500 ; Retract filament",
|
||||
"G90 ; Set absolute positioning",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"M109 S170 ; Set nozzle temperature",
|
||||
"G0 X108 Y-0.5 F30000 ; Move to wipe position",
|
||||
"G380 S3 Z-5 F1200 ; Lower nozzle to wipe",
|
||||
"; Repeat wipe sequence",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X110 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"M73 P22 R18",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X112 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X114 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X116 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X118 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X120 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X122 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X124 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X126 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X128 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X130 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X132 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X134 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X136 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X138 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X140 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X142 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X144 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X146 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z2 F1200",
|
||||
"G1 X148 F10000",
|
||||
"G380 S3 Z-5 F1200",
|
||||
"G1 Z5 F30000 ; Raise Z",
|
||||
";===== Remove Waste by Touching End ===============",
|
||||
"G1 Z10 F1200 ; Lift nozzle",
|
||||
"G0 X118 Y261 F30000 ; Move to safe position",
|
||||
"G1 Z5 F1200 ; Lower Z",
|
||||
"M109 S170 ; Set nozzle temperature",
|
||||
"G28 Z P0 T300 ; Home Z with low precision",
|
||||
"G29.2 S0 ; Turn off ABL",
|
||||
"M104 S140 ; Prepare for auto bed leveling",
|
||||
"G0 Z5 F20000 ; Lift Z",
|
||||
"G0 X128 Y261 F20000 ; Move to exposed steel surface",
|
||||
"G0 Z-1.01 F1200 ; Lower nozzle to stop position",
|
||||
"G91 ; Set relative positioning",
|
||||
"G2 I1 J0 X2 Y0 F2000.1 ; Execute wipe motion",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 Z10 F1200 ; Raise Z",
|
||||
";===== Brush Material Wipe Nozzle =================",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 Y250 F30000 ; Move to brush position",
|
||||
"G1 X55 ; Move X",
|
||||
"G1 Z1.300 F1200 ; Lower nozzle",
|
||||
"G1 Y262.5 F6000 ; Wipe motion",
|
||||
"G91 ; Set relative positioning",
|
||||
"G1 X-35 F30000 ; Move along brush",
|
||||
"G1 Y-0.5",
|
||||
"G1 X45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X-45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X-45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X45",
|
||||
"G1 Z5.000 F1200 ; Lift nozzle",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 X30 Y250.000 F30000 ; Move to brush position",
|
||||
"G1 Z1.300 F1200 ; Lower nozzle",
|
||||
"G1 Y262.5 F6000 ; Wipe motion",
|
||||
"G91 ; Set relative positioning",
|
||||
"G1 X35 F30000 ; Move along brush",
|
||||
"G1 Y-0.5",
|
||||
"G1 X-45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X-45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X45",
|
||||
"G1 Y-0.5",
|
||||
"G1 X-45",
|
||||
"G1 Z10.000 F1200 ; Lift nozzle",
|
||||
";===== Brush Material Wipe Nozzle End ============",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 Y250 F30000 ; Move to safe position",
|
||||
"G1 X138",
|
||||
"G1 Y261",
|
||||
"G0 Z-1.01 F1200 ; Stop nozzle",
|
||||
"G91 ; Set relative positioning",
|
||||
"G2 I1 J0 X2 Y0 F2000.1",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"M73 P23 R18",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"G2 I1 J0 X2",
|
||||
"G2 I-0.75 J0 X-1.5",
|
||||
"M109 S140 ; Set nozzle temperature",
|
||||
"M106 S255 ; Turn on fan (G28 turns off fan)",
|
||||
"M211 R ; Restore soft endstop status",
|
||||
";===== Bed Leveling ===============================",
|
||||
"M1002 judge_flag g29_before_print_flag ; Check bed leveling flag",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 Z5 F1200 ; Lift nozzle",
|
||||
"G1 X0 Y0 F30000 ; Move to home position",
|
||||
"G29.2 S1 ; Turn on ABL (auto bed level)",
|
||||
"M190 S65 ; Set and wait bed temp",
|
||||
"M109 S140 ; Set and wait nozzle temp",
|
||||
"M106 S0 ; Turn off fan (reduce noise)",
|
||||
"M622 J1",
|
||||
" M1002 gcode_claim_action : 1",
|
||||
" G29 A1 X115.2 Y115.2 I25.6 J25.6 ; Perform bed leveling",
|
||||
" M400 ; Wait for all movements to complete",
|
||||
" M500 ; Save calibration data",
|
||||
"M623 ; End bed leveling",
|
||||
";===== Home After Wipe Mouth ======================",
|
||||
"M1002 judge_flag g29_before_print_flag ; Check if bed leveling was performed",
|
||||
"M622 J0 ; Signal start of sequence",
|
||||
" M1002 gcode_claim_action : 13 ; Claim home action",
|
||||
" G28 ; Home all axes",
|
||||
"M623 ; End sequence",
|
||||
";===== Home After Wipe Mouth End ==================",
|
||||
"G1 X108.000 Y-0.500 F30000 ; Move to start position",
|
||||
"G1 Z0.300 F1200 ; Lower Z for calibration",
|
||||
"M400 ; Wait for moves to complete",
|
||||
"G2814 Z0.32 ; Set Z offset",
|
||||
"M104 S220 ; Set nozzle temperature to 220°C for printing",
|
||||
";===== Nozzle Load Line ===========================",
|
||||
";G90 ; Set absolute positioning",
|
||||
";M83 ; Set extruder to relative mode",
|
||||
";G1 Z5 F1200 ; Raise Z before priming",
|
||||
";G1 X88 Y-0.5 F20000 ; Move to priming position",
|
||||
";G1 Z0.3 F1200 ; Lower nozzle for priming",
|
||||
";M109 S220 ; Wait for nozzle temperature to reach 220°C",
|
||||
";G1 E2 F300 ; Extrude filament",
|
||||
";G1 X168 E4.989 F6000 ; Extrude along the line",
|
||||
";G1 Z1 F1200 ; Lift Z after priming",
|
||||
";===== Extrude Calibration Test ====================",
|
||||
"M400 ; Wait for all commands to complete",
|
||||
"M900 S ; Start calibration",
|
||||
"M900 C ; Start calibration cycle",
|
||||
"G90 ; Set absolute positioning",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"M109 S220 ; Set and wait nozzle temp",
|
||||
"G0 X128 E8 F904.991 ; Extrude calibration test pattern",
|
||||
"G0 X133 E.3742 F1508.32",
|
||||
"G0 X138 E.3742 F6033.27",
|
||||
"G0 X143 E.3742 F1508.32",
|
||||
"G0 X148 E.3742 F6033.27",
|
||||
"G0 X153 E.3742 F1508.32",
|
||||
"G91 ; Set relative positioning",
|
||||
"G1 X1 Z-0.300 ; Lower nozzle slightly",
|
||||
"G1 X4 ",
|
||||
"G1 Z1 F1200 ; Raise back up",
|
||||
"G90 ; Return to absolute positioning",
|
||||
"M400 ; Wait for completion",
|
||||
"M900 R ; Reset calibration",
|
||||
"M1002 judge_flag extrude_cali_flag ; Check calibration status",
|
||||
"M622 J1 ; Signal start of sequence",
|
||||
" G90 ; Set absolute positioning",
|
||||
" G1 X108.000 Y1.000 F30000 ; Move to calibration position",
|
||||
" G91 ; Set relative positioning",
|
||||
" G1 Z-0.700 F1200 ; Lower nozzle slightly",
|
||||
" G90 ; Return to absolute positioning",
|
||||
" M83 ; Set extruder to relative mode",
|
||||
" G0 X128 E10 F904.991 ; Run extrusion test",
|
||||
" G0 X133 E.3742 F1508.32",
|
||||
" G0 X138 E.3742 F6033.27",
|
||||
" G0 X143 E.3742 F1508.32",
|
||||
" G0 X148 E.3742 F6033.27",
|
||||
" G0 X153 E.3742 F1508.32",
|
||||
" G91 ; Set relative positioning",
|
||||
" G1 X1 Z-0.300 ; Lower slightly",
|
||||
" G1 X4",
|
||||
" G1 Z1 F1200 ; Raise back up",
|
||||
" G90 ; Return to absolute positioning",
|
||||
" M400 ; Wait for completion",
|
||||
"M623 ; End sequence",
|
||||
"G1 Z0.2 ; Lower nozzle slightly",
|
||||
"M1002 gcode_claim_action : 0 ; Claim action",
|
||||
"M400 ; Wait for actions to complete",
|
||||
";===== Final Print Prep ===========================",
|
||||
"G29.1 Z-0.02 ; Adjust Z height for PEI plate",
|
||||
"M960 S1 P0 ; Turn off laser",
|
||||
"M960 S2 P0 ; Turn off laser",
|
||||
"M106 S0 ; Turn off fan",
|
||||
"M106 P2 S0 ; Turn off auxiliary fan",
|
||||
"M106 P3 S0 ; Turn off chamber fan",
|
||||
"M975 S1 ; Enable mechanical suppression mode",
|
||||
"G90 ; Set absolute positioning",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"T1000 ; Tool selection",
|
||||
"M211 X0 Y0 Z0 ; Disable soft endstops",
|
||||
"M1007 S1 ; Enable mass estimation",
|
||||
"G29.4 ; Start additional leveling routine",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G21 ; Set units to millimeters",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"M106 P3 S200 ; Set chamber fan speed",
|
||||
"M981 S1 P20000 ; Open spaghetti detector"
|
||||
],
|
||||
"gcodePost": [
|
||||
"; ===== FOOTER ====================================",
|
||||
"M204 S6000 ; Set acceleration to 6000 mm/s^2",
|
||||
"M1003 S0 ; Disable power loss recovery",
|
||||
"M106 S0 ; Turn off part cooling fan",
|
||||
"M106 P2 S0 ; Turn off auxiliary cooling fan",
|
||||
"M981 S0 P20000 ; Close spaghetti detector",
|
||||
"M106 P3 S0 ; Turn off chamber fan",
|
||||
"G392 S0 ; Turn off nozzle clog detection",
|
||||
"M400 ; Wait for buffer to clear",
|
||||
"G92 E0 ; Zero the extruder",
|
||||
"G1 E-0.8 F1800 ; Retract filament by 0.8mm",
|
||||
"G1 Z26.1 F900 ; Raise Z-axis slightly",
|
||||
"G1 X0 Y128 F18000 ; Move to safe position",
|
||||
"G1 X-13.0 F3000 ; Move to another safe position",
|
||||
";===== Timelapse Sequence ========================",
|
||||
"M1002 judge_flag timelapse_record_flag",
|
||||
"M622 J1",
|
||||
" M400 P100",
|
||||
" M971 S11 C11 O0",
|
||||
" M991 S0 P-1 ; End timelapse at safe pos",
|
||||
"M623 ; End conditional block",
|
||||
";===== End of Print ==============================",
|
||||
"M140 S0 ; Turn off heated bed",
|
||||
"M106 S0 ; Turn off all cooling fans",
|
||||
"M106 P2 S0 ; Turn off auxiliary cooling fan",
|
||||
"M106 P3 S0 ; Turn off chamber cooling fan",
|
||||
";===== Pull Back Filament ========================",
|
||||
"M620 S255 ; Retract filament to AMS",
|
||||
"G1 X267 F15000 ; Move to retract position",
|
||||
"T255 ; Select tool 255 (AMS retract)",
|
||||
"G1 X-28.5 F18000",
|
||||
"G1 X-48.2 F3000",
|
||||
"G1 X-28.5 F18000",
|
||||
"G1 X-48.2 F3000",
|
||||
"M621 S255 ; Complete filament unload",
|
||||
"M104 S0 ; Turn off hotend",
|
||||
"M400 ; Wait for all motion to complete",
|
||||
"M17 S ; Enable steppers",
|
||||
"M17 Z0.4 ; Reduce Z stepper motor current",
|
||||
"G1 Z125.6 F600 ; Move Z-axis up",
|
||||
"G1 Z123.6",
|
||||
"M400 P100",
|
||||
"M17 R ; Restore Z motor current",
|
||||
"G90 ; Set absolute positioning",
|
||||
"G1 X-48 Y180 F3600 ; Move to parking position",
|
||||
"M220 S100 ; Reset feedrate to 100%",
|
||||
"M201.2 K1.0 ; Reset acceleration magnitude",
|
||||
"M73.2 R1.0 ; Reset remaining time estimation",
|
||||
"M1002 set_gcode_claim_speed_level : 0",
|
||||
";===== Printer Finish Sound ======================",
|
||||
"M17",
|
||||
"M400 S1",
|
||||
"M1006 S1",
|
||||
"M1006 A0 B20 L100 C37 D20 M40 E42 F20 N60",
|
||||
"M1006 A0 B10 L100 C44 D10 M60 E44 F10 N60",
|
||||
"M1006 A0 B10 L100 C46 D10 M80 E46 F10 N80",
|
||||
"M1006 A44 B20 L100 C39 D20 M60 E48 F20 N60",
|
||||
"M1006 A0 B10 L100 C44 D10 M60 E44 F10 N60",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N60",
|
||||
"M1006 A0 B10 L100 C39 D10 M60 E39 F10 N60",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N60",
|
||||
"M1006 A0 B10 L100 C44 D10 M60 E44 F10 N60",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N60",
|
||||
"M1006 A0 B10 L100 C39 D10 M60 E39 F10 N60",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N60",
|
||||
"M1006 A0 B10 L100 C48 D10 M60 E44 F10 N80",
|
||||
"M1006 A0 B10 L100 C0 D10 M60 E0 F10 N80",
|
||||
"M1006 A44 B20 L100 C49 D20 M80 E41 F20 N80",
|
||||
"M1006 A0 B20 L100 C0 D20 M60 E0 F20 N80",
|
||||
"M1006 A0 B20 L100 C37 D20 M30 E37 F20 N60",
|
||||
"M1006 W",
|
||||
"M400 ; Wait for completion",
|
||||
"M18 X Y Z ; Disable motors",
|
||||
"M73 P100 R0 ; Set progress to 100%"
|
||||
],
|
||||
"gcodeProc": "",
|
||||
"gcodeFExt": "gcode",
|
||||
"extruders": [
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [
|
||||
"; BEGIN tool change from {last_tool} to {tool}",
|
||||
"M620 S{tool}A ; prepare tool change",
|
||||
"M204 S9000 ; Set acceleration to 9000 mm/s^2",
|
||||
"M620.11 S0 ; Reset filament retraction state",
|
||||
"M400 ; wait for moves to complete",
|
||||
"M620.1 E F187 T240 ; set purge rate 187 temp 240",
|
||||
"T{tool} ; initiate tool change",
|
||||
"M620.1 E F374 T240 ; set purge rate 374 temp 240",
|
||||
"M620.11 S0 ; Reset filament retraction state",
|
||||
"G92 E0 ; reset extruder position",
|
||||
"M400 ; wait for moves to complete",
|
||||
" ; -- PURGE --",
|
||||
"M109 S240 ; Set nozzle temperature to 240°C",
|
||||
"G1 E23.7 F187 ; Extrude 23.7mm of filament at flowrate 187",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F187 ; Continue extrusion at flowrate 187",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F374 ; Continue extrusion at flowrate 374",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F374 ; Continue extrusion at flowrate 374",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F374 ; Continue extrusion at flowrate 374",
|
||||
"G1 E-2 F1800 ; Retract filament by 2mm",
|
||||
"G1 E2 F300 ; Recover retraction slowly",
|
||||
"M400 ; Wait for all movements to complete",
|
||||
"M109 S{temp} ; Set nozzle temperature back to print temp",
|
||||
" ; -- SHAKE --",
|
||||
"M106 P1 S255 ; Turn on nozzle cooling fan to full speed",
|
||||
"M400 S3 ; Wait for all movements to complete",
|
||||
"G1 X70 F5000 ; Move to X70",
|
||||
"G1 X90 F3000 ; Move to X90",
|
||||
"G1 Y255 F4000 ; Move to Y255",
|
||||
"G1 X105 F5000 ; Move to X105",
|
||||
"G1 Y265 ; Move to Y265",
|
||||
"G1 X70 F10000 ; Rapid move to X70",
|
||||
"G1 X100 F5000 ; Move to X100",
|
||||
"G1 X70 F10000 ; Rapid move back to X70",
|
||||
"G1 X100 F5000 ; Move to X100",
|
||||
"G1 X70 F10000 ; Shake filament residue",
|
||||
"G1 X80 F15000",
|
||||
"G1 X60",
|
||||
"G1 X80",
|
||||
"G1 X60",
|
||||
"G1 X80 ; Shake to put down garbage",
|
||||
"G1 X100 F5000 ; Move to X100",
|
||||
"G1 X165 F15000 ; Wipe and shake",
|
||||
"G1 Y256 ; Move Y aside to prevent collision",
|
||||
"M400 ; Wait for all movements to complete",
|
||||
"M204 S10000 ; Set acceleration to 10000 mm/s^2",
|
||||
"M621 S{tool}A ; complete tool change",
|
||||
"; END tool change"
|
||||
],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
},
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
},
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
},
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [
|
||||
"T3"
|
||||
],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
}
|
||||
],
|
||||
"new": false,
|
||||
"deviceName": "Bambu A1",
|
||||
"bedBelt": false,
|
||||
"fwRetract": false,
|
||||
"filamentSource": "direct",
|
||||
"extras": {
|
||||
"bbl": {
|
||||
"luke": {
|
||||
"host": "10.20.20.75",
|
||||
"code": "20709223",
|
||||
"serial": "01P09C482401767",
|
||||
"modified": true
|
||||
},
|
||||
"leia": {
|
||||
"host": "10.20.20.76",
|
||||
"code": "35129782",
|
||||
"serial": "01P09C461603301",
|
||||
"modified": true
|
||||
},
|
||||
"vader": {
|
||||
"host": "10.20.20.77",
|
||||
"code": "17711811",
|
||||
"serial": "01P00A413100516",
|
||||
"modified": true
|
||||
},
|
||||
"Scrappy": {
|
||||
"host": "10.10.2.5",
|
||||
"serial": "01S00A292500224",
|
||||
"code": "38373335",
|
||||
"modified": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"imageURL": "",
|
||||
"deviceZMax": 0,
|
||||
"gcodeTime": 1,
|
||||
"gcodeFeature": [],
|
||||
"profiles": [
|
||||
{
|
||||
"processName": "Bambu PLA",
|
||||
"sliceHeight": 0.2,
|
||||
"sliceShells": 2,
|
||||
"sliceShellOrder": "in-out",
|
||||
"sliceLayerStart": "last",
|
||||
"sliceFillAngle": 45,
|
||||
"sliceFillOverlap": 0.35,
|
||||
"sliceFillSparse": 0.1,
|
||||
"sliceFillType": "grid",
|
||||
"sliceAdaptive": false,
|
||||
"sliceMinHeight": 0,
|
||||
"sliceSupportDensity": 0.5,
|
||||
"sliceSupportOffset": 0.4,
|
||||
"sliceSupportGap": 1,
|
||||
"sliceSupportSize": 5,
|
||||
"sliceSupportArea": 0.25,
|
||||
"sliceSupportExtra": 0,
|
||||
"sliceSupportAngle": 60,
|
||||
"sliceSupportNozzle": 0,
|
||||
"sliceSolidMinArea": 0,
|
||||
"sliceBottomLayers": 2,
|
||||
"sliceTopLayers": 3,
|
||||
"firstLayerRate": 20,
|
||||
"firstLayerPrintMult": 1,
|
||||
"firstLayerYOffset": 0,
|
||||
"firstLayerBrim": 0,
|
||||
"firstLayerBeltLead": 0,
|
||||
"firstLayerFanSpeed": 0,
|
||||
"outputTemp": 220,
|
||||
"outputBedTemp": 65,
|
||||
"outputFanSpeed": 255,
|
||||
"outputFeedrate": 120,
|
||||
"outputFinishrate": 90,
|
||||
"outputSeekrate": 200,
|
||||
"outputShellMult": 1.2,
|
||||
"outputFillMult": 1.2,
|
||||
"outputSparseMult": 1.2,
|
||||
"outputRetractDist": 2,
|
||||
"outputRetractSpeed": 80,
|
||||
"outputRetractWipe": 0,
|
||||
"outputRetractDwell": 0,
|
||||
"outputShortPoly": 100,
|
||||
"outputMinSpeed": 5,
|
||||
"outputCoastDist": 0,
|
||||
"outputLayerRetract": false,
|
||||
"zHopDistance": 0,
|
||||
"antiBacklash": 0,
|
||||
"sliceFillWidth": 1,
|
||||
"sliceFillRate": 0,
|
||||
"sliceSupportEnable": false,
|
||||
"firstSliceHeight": 0.3,
|
||||
"firstLayerFillRate": 80,
|
||||
"firstLayerLineMult": 1,
|
||||
"firstLayerNozzleTemp": 210,
|
||||
"firstLayerBedTemp": 60,
|
||||
"firstLayerBrimTrig": 0,
|
||||
"firstLayerBrimGap": 0,
|
||||
"outputRaft": false,
|
||||
"outputRaftSpacing": 0.2,
|
||||
"outputBrimCount": 0,
|
||||
"outputBrimOffset": 2,
|
||||
"outputPurgeTower": 0,
|
||||
"outputInvertX": false,
|
||||
"outputInvertY": false,
|
||||
"arcTolerance": 0,
|
||||
"ranges": [],
|
||||
"sliceLineWidth": 0,
|
||||
"sliceFillRepeat": 2,
|
||||
"firstLayerBrimIn": 0,
|
||||
"firstLayerBeltBump": 0,
|
||||
"outputBeltFirst": false,
|
||||
"outputLoops": 0,
|
||||
"sliceFillGrow": 0,
|
||||
"sliceSolidRate": 0,
|
||||
"sliceSupportSpan": 5,
|
||||
"sliceSupportOutline": true,
|
||||
"firstLayerFlatten": 0,
|
||||
"outputDraftShield": false,
|
||||
"outputAvoidGaps": true,
|
||||
"sliceDetectThin": "off",
|
||||
"outputAlternating": false,
|
||||
"sliceLayerStartX": 0,
|
||||
"sliceLayerStartY": 0,
|
||||
"sliceSupportGrow": 0,
|
||||
"outputFanLayer": 1,
|
||||
"outputNozzle": 0,
|
||||
"sliceAngle": 45,
|
||||
"sliceZInterleave": false
|
||||
}
|
||||
]
|
||||
}
|
||||
508
src/kiri-dev/fdm/Bambu.P1S
Normal file
508
src/kiri-dev/fdm/Bambu.P1S
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
{
|
||||
"mode": "FDM",
|
||||
"internal": 0,
|
||||
"bedHeight": 2.5,
|
||||
"bedWidth": 256,
|
||||
"bedDepth": 256,
|
||||
"bedRound": false,
|
||||
"maxHeight": 256,
|
||||
"originCenter": false,
|
||||
"gcodeFan": [
|
||||
"M106 S{fan_speed}"
|
||||
],
|
||||
"gcodeTrack": [
|
||||
"M73 P{progress}"
|
||||
],
|
||||
"gcodeLayer": [
|
||||
"; CHANGE_LAYER",
|
||||
"; layer num/total_layer_count: {layer}/{layers}",
|
||||
"M622.1 S1 ; for prev firware, default turned on",
|
||||
"M1002 judge_flag timelapse_record_flag",
|
||||
"M622 J1",
|
||||
"M971 S11 C10 O0 ; timelapse without wipe tower",
|
||||
"M623",
|
||||
"M73 L{layer}",
|
||||
"M991 S0 P{layer-1} ; notify layer change",
|
||||
";; IF { layer == 0 }",
|
||||
"M204 S500",
|
||||
";; ELSE",
|
||||
"M204 S5000",
|
||||
";; END"
|
||||
],
|
||||
"gcodePre": [
|
||||
"; total layer number: {layers}",
|
||||
";; PREAMBLE",
|
||||
";; DEFINE BAMBU-AMS 0,1,2,3",
|
||||
"; ===== HEADER ====================================",
|
||||
"M73 P0 R14 ; Initial progress and time remaining",
|
||||
"M201 X20000 Y20000 Z500 E5000 ; Set max acceleration",
|
||||
"M203 X500 Y500 Z20 E30 ; Set max feedrates",
|
||||
"M204 P20000 R5000 T20000 ; Set acceleration",
|
||||
"M205 X9.00 Y9.00 Z3.00 E2.50 ; Set advanced settings",
|
||||
"M106 S0 ; Set part fan speed to 0",
|
||||
"M106 P2 S0 ; Turn off aux fan",
|
||||
";===== machine: P1S ===============================",
|
||||
"M104 S75 ; Set extruder temp to turn on HB fan, prevent filament oozing",
|
||||
"M710 A1 S255 ; Turn on MC fan by default (P1S)",
|
||||
";===== Reset machine status =======================",
|
||||
"M290 X40 Y40 Z2.6666666 ; Set baby stepping offsets",
|
||||
"G91 ; Set to relative positioning",
|
||||
"M17 Z0.4 ; Lower Z-motor current",
|
||||
"G380 S2 Z30 F300 ; Lower hotbed (G380 like G38)",
|
||||
"G380 S2 Z-25 F300 ; Continue to lower bed",
|
||||
"G1 Z5 F300 ; Raise bed by 5mm",
|
||||
"G90 ; Set to absolute positioning",
|
||||
"M17 X1.2 Y1.2 Z0.75 ; Reset motor current to default",
|
||||
"M960 S5 P1 ; Turn on logo lamp",
|
||||
"M220 S100 ; Reset feedrate to 100%",
|
||||
"M221 S100 ; Reset flowrate to 100%",
|
||||
"M73.2 R1.0 ; Reset remaining time magnitude",
|
||||
"M1002 set_gcode_claim_speed_level : 5 ; Set G-code claim speed level",
|
||||
"M221 X0 Y0 Z0 ; Turn off soft endstop to prevent logic issues",
|
||||
"G29.1 Z0 ; Clear Z-trim value",
|
||||
"M204 S10000 ; Initialize acceleration to 10 m/s^2",
|
||||
";===== Heatbed preheat ============================",
|
||||
"M1002 gcode_claim_action : 2 ; Claim action for preheating",
|
||||
"M140 S{bed_temp} ; Set bed temperature to 65°C",
|
||||
"M190 S{bed_temp} ; Wait for bed temperature to reach 65°C",
|
||||
";===== Turn on fans to prevent PLA jamming ========",
|
||||
"M106 P3 S180 ; set chamber fan speed to prevent PLA jamming",
|
||||
"M106 P2 S100 ; Turn on aux fan to cool toolhead",
|
||||
";===== Prepare print temperature and material =====",
|
||||
"M104 S{temp} ; Set extruder temp to 220°C",
|
||||
"G91 ; Set to relative positioning",
|
||||
"G0 Z10 F1200 ; Raise Z by 10mm",
|
||||
"G90 ; Set to absolute positioning",
|
||||
"G28 X ; Home X axis",
|
||||
"M975 S1 ; Turn on feature (guess: vibration suppression)",
|
||||
"M73 P31 R9 ; Update progress and time remaining",
|
||||
"G1 X60 F12000 ; Fast move to X60",
|
||||
"G1 Y245 ; Move to Y245",
|
||||
"G1 Y265 F3000 ; Move to Y265 slowly",
|
||||
"M620 M ; Custom command (guess: material management)",
|
||||
"M620 S{tool}A ; Switch material if AMS present",
|
||||
"M109 S{temp} ; Wait for extruder to reach 220°C",
|
||||
"G1 X120 F12000 ; Fast move to X120",
|
||||
"G1 X20 Y50 F12000 ; Fast move to X20 Y50",
|
||||
"G1 Y-3 ; Move down Y-axis by 3",
|
||||
"T{tool} ; Select starting tool",
|
||||
"G1 X54 F12000 ; Fast move to X54",
|
||||
"G1 Y265 ; Move to Y265",
|
||||
"M400 ; Wait for all moves to finish",
|
||||
"M621 S{tool}A ; Custom command",
|
||||
"M620.1 E F523.843 T240 ; Extrude command with custom parameters",
|
||||
"M412 S1 ; Turn on filament runout detection",
|
||||
"M109 S250 ; Set nozzle to common flush temp",
|
||||
"M106 P1 S0 ; Turn off nozzle fan",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E50 F200 ; Extrude 50mm of filament",
|
||||
"M400 ; Wait for moves to finish",
|
||||
"M104 S{temp} ; Set nozzle temperature to 220°C",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E50 F200 ; Extrude 50mm of filament",
|
||||
"M400 ; Wait for moves to finish",
|
||||
"M106 P1 S255 ; Turn on nozzle fan to full speed",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E5 F300 ; Extrude 5mm of filament",
|
||||
"M109 S200 ; Drop nozzle temperature to 200°C to shrink filament",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"M73 P33 R9 ; Update progress and time remaining",
|
||||
"G1 E-0.5 F300 ; Retract filament by 0.5mm",
|
||||
"M73 P35 R9 ; Update progress and time remaining",
|
||||
"G1 X70 F9000 ; Fast move to X70",
|
||||
"G1 X76 F15000 ; Fast move to X76",
|
||||
"G1 X65 F15000 ; Fast move to X65",
|
||||
"G1 X76 F15000 ; Repeat movements to shake and clean nozzle",
|
||||
"G1 X65 F15000 ; Shake to put down garbage",
|
||||
"G1 X80 F6000 ; Move to X80",
|
||||
"G1 X95 F15000 ; Fast move to X95",
|
||||
"G1 X80 F15000 ; Return to X80",
|
||||
"G1 X165 F15000 ; Wipe and shake",
|
||||
"M400 ; Wait for all moves to finish",
|
||||
"M106 P1 S0 ; Turn off nozzle fan",
|
||||
";===== Wipe nozzle ===============================",
|
||||
"M1002 gcode_claim_action : 14 ; Claim action for nozzle wipe",
|
||||
"M975 S1 ; Turn on feature (guess: vibration suppression)",
|
||||
"M106 S255 ; Turn on part fan to full speed",
|
||||
"G1 X65 Y230 F18000 ; Fast move to start of wipe",
|
||||
"G1 Y264 F6000 ; Wipe along Y-axis",
|
||||
"M109 S200 ; Set nozzle temperature to 200°C",
|
||||
"G1 X100 F18000 ; Wipe first pass",
|
||||
"G0 X135 Y253 F20000 ; Move to exposed steel surface edge",
|
||||
"G28 Z P0 T300 ; Home Z with low precision, permit 300°C temperature",
|
||||
"G29.2 S0 ; Turn off ABL (auto bed leveling)",
|
||||
"G0 Z5 F20000 ; Raise Z by 5mm",
|
||||
"G1 X60 Y265 ; Move to X60 Y265",
|
||||
"G92 E0 ; Reset extruder position",
|
||||
"G1 E-0.5 F300 ; Retract more",
|
||||
"G1 X100 F5000 ; Second wipe pass",
|
||||
"G1 X70 F15000 ; Repeat movements for further wiping",
|
||||
"G1 X100 F5000",
|
||||
"G1 X70 F15000",
|
||||
"G1 X100 F5000",
|
||||
"G1 X70 F15000",
|
||||
"G1 X90 F5000",
|
||||
"G0 X128 Y261 Z-1.5 F20000 ; Move to exposed steel surface, stop nozzle",
|
||||
"M104 S140 ; Set temp down to heatbed acceptable",
|
||||
"M106 S255 ; Turn on part fan (G28 turned off fan)",
|
||||
"M221 S ; Push soft endstop status",
|
||||
"M221 Z0 ; Turn off Z axis endstop",
|
||||
"G0 Z0.5 F20000 ; Raise Z by 0.5mm",
|
||||
"G0 X125 Y259.5 Z-1.01 ; Move to wiping position",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 Z0.5 F20000",
|
||||
"G0 X125 Y262.5",
|
||||
"G0 Z-1.01",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 Z0.5 F20000",
|
||||
"G0 X125 Y260.0",
|
||||
"G0 Z-1.01",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 Z0.5 F20000",
|
||||
"G0 X125 Y262.0",
|
||||
"G0 Z-1.01",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 Z0.5 F20000",
|
||||
"G0 X125 Y260.5",
|
||||
"G0 Z-1.01",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 Z0.5 F20000",
|
||||
"G0 X125 Y261.5",
|
||||
"G0 Z-1.01",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 Z0.5 F20000",
|
||||
"G0 X125 Y261.0",
|
||||
"G0 Z-1.01",
|
||||
"G0 X131 F211",
|
||||
"G0 X124",
|
||||
"G0 X128",
|
||||
"G2 I0.5 J0 F300 ; Arc movements for final cleaning",
|
||||
"M73 P36 R8 ; Update progress and time remaining",
|
||||
"G2 I0.5 J0 F300",
|
||||
"G2 I0.5 J0 F300",
|
||||
"G2 I0.5 J0 F300",
|
||||
"M109 S140 ; Wait for nozzle temp to reach heatbed acceptable temp",
|
||||
"G2 I0.5 J0 F3000 ; More arc movements",
|
||||
"G2 I0.5 J0 F3000",
|
||||
"G2 I0.5 J0 F3000",
|
||||
"G2 I0.5 J0 F3000",
|
||||
"M221 R ; Pop softend status",
|
||||
"G1 Z10 F1200 ; Raise Z by 10mm",
|
||||
"M400 ; Wait for all moves to finish",
|
||||
"G1 Z10 ; Raise Z by 10mm",
|
||||
"G1 F30000 ; Set fast feedrate",
|
||||
"G1 X230 Y15 ; Move to X230 Y15",
|
||||
"G29.2 S1 ; Turn on ABL",
|
||||
"M106 S0 ; Turn off part fan, too noisy",
|
||||
";===== Bed leveling ===============================",
|
||||
"M1002 judge_flag g29_before_print_flag ; Judge flag before leveling",
|
||||
"M622 J1",
|
||||
" M1002 gcode_claim_action : 1 ; Claim action for bed leveling",
|
||||
" G29 A X115.2 Y115.2 I25.6 J25.6 ; Perform bed leveling",
|
||||
" M400 ; Wait for all moves to finish",
|
||||
" M500 ; Save calibration data",
|
||||
"M623",
|
||||
";===== Home after wipe mouth ======================",
|
||||
"M1002 judge_flag g29_before_print_flag ; Judge flag before homing",
|
||||
"M622 J0",
|
||||
" M1002 gcode_claim_action : 13 ; Claim action for homing",
|
||||
" G28 ; Home all axes",
|
||||
"M623",
|
||||
";===== Home after wipe mouth end ==================",
|
||||
"M975 S1 ; Turn on vibration suppression",
|
||||
";===== Turn on fans to prevent PLA jamming ========",
|
||||
"M106 P3 S180 ; set chamber fan speed to Prevent PLA jamming",
|
||||
"M106 P2 S100 ; Turn on aux fan to cool toolhead",
|
||||
"M104 S{temp} ; Set extruder temp to 220°C earlier, reduce wait time",
|
||||
";===== Mech mode fast check =======================",
|
||||
"G1 X128 Y128 Z10 F20000 ; Move to center for check",
|
||||
"M400 P200 ; Wait and perform check",
|
||||
"M970.3 Q1 A7 B30 C80 H15 K0 ; Custom command (mechanical check)",
|
||||
"M974 Q1 S2 P0 ; Custom command",
|
||||
"G1 X128 Y128 Z10 F20000 ; Move again to center for check",
|
||||
"M400 P200 ; Wait and perform another check",
|
||||
"M970.3 Q0 A7 B30 C90 Q0 H15 K0 ; Custom command",
|
||||
"M974 Q0 S2 P0 ; Custom command",
|
||||
"M975 S1 ; Turn on feature (vibration suppression)",
|
||||
"G1 F30000 ; Set fast feedrate",
|
||||
"M73 P37 R8 ; Update progress and time remaining",
|
||||
"G1 X230 Y15 ; Move to X230 Y15",
|
||||
"G28 X ; Re-home X and Y",
|
||||
";===== Nozzle load line ===========================",
|
||||
"M975 S1 ; Turn on vibration suppression",
|
||||
"G90 ; Set to absolute positioning",
|
||||
"M83 ; Set extruder to relative mode",
|
||||
"T1000 ; Custom command",
|
||||
"G1 X18.0 Y1.0 Z0.8 F18000 ; Move to start position",
|
||||
"M109 S{temp} ; Set extruder temp to 220°C",
|
||||
"G1 Z0.2 ; Lower Z to 0.2mm",
|
||||
"G0 E2 F300 ; Extrude 2mm of filament",
|
||||
"G0 X240 E25 F5539.96 ; Move to X240 while extruding",
|
||||
"G0 Y15 E1.166 F1384.99 ; Move along Y while extruding",
|
||||
"G0 X239.5 ; Move to X239.5",
|
||||
"G0 E0.2 ; Extrude 0.2mm of filament",
|
||||
"G0 Y1.5 E1.166 ; Move along Y while extruding",
|
||||
"G0 X18 E25 F5539.96 ; Return to start position while extruding",
|
||||
"M400 ; Wait for all moves to finish",
|
||||
";===== Final Preparations =========================",
|
||||
";G29.1 Z-0.04 ; Adjust Z-offset for textured PEI plate",
|
||||
"M1002 gcode_claim_action : 0 ; Claim action",
|
||||
"M106 S0 ; Turn off part fan",
|
||||
"M106 P2 S0 ; Turn off aux fan",
|
||||
"M106 P3 S0 ; Turn off chamber fan",
|
||||
"M975 S1 ; Turn on mechanical mode suppression",
|
||||
"G90 ; Set to absolute positioning",
|
||||
"G21 ; Set units to millimeters",
|
||||
"M83 ; Use relative distances for extrusion",
|
||||
"M106 P3 S200 ; Set chamber fan speed to 200 (prevents PLA jamming)",
|
||||
"M981 S1 P20000 ; Open spaghetti detector"
|
||||
],
|
||||
"gcodePost": [
|
||||
"; ===== FOOTER =====================================",
|
||||
"M106 S0 ; Turn off part cooling fan",
|
||||
"M106 P2 S0 ; Turn off auxiliary cooling fan",
|
||||
"M981 S0 P20000 ; Close spaghetti detector",
|
||||
"M106 P3 S0 ; Turn off chamber fan",
|
||||
"M400 ; Wait for buffer to clear",
|
||||
"G92 E0 ; Zero the extruder",
|
||||
"G1 E-0.8 F1800 ; Retract filament by 0.8mm",
|
||||
"G91 ; relative moves",
|
||||
"G1 Z5 F900 ; drop bed 5mm",
|
||||
"G90 ; absolute moves",
|
||||
"G1 X65 Y245 F12000 ; Move to safe position X65 Y245",
|
||||
"G1 Y265 F3000 ; Move to Y265 slowly",
|
||||
"G1 X65 Y245 F12000 ; Move back to X65 Y245",
|
||||
"G1 Y265 F3000 ; Move to Y265 slowly again",
|
||||
"M140 S0 ; Turn off heated bed",
|
||||
"M106 S0 ; Turn off part cooling fan",
|
||||
"M106 P2 S0 ; Turn off auxiliary cooling fan",
|
||||
"M106 P3 S0 ; Turn off chamber cooling fan",
|
||||
"G1 X100 F12000 ; Wipe nozzle",
|
||||
"M620 S255 ; Pull back filament to AMS",
|
||||
"G1 X20 Y50 F12000 ; Move to X20 Y50",
|
||||
"G1 Y-3 ; Move down Y-axis by 3",
|
||||
"T255 ; Tool change command (educated guess)",
|
||||
"G1 X65 F12000 ; Move to X65",
|
||||
"G1 Y265 ; Move to Y265",
|
||||
"G1 X100 F12000 ; Wipe nozzle again",
|
||||
"M621 S255 ; Custom command (educated guess)",
|
||||
"M104 S0 ; Turn off hotend",
|
||||
"M622.1 S1 ; Enable feature (default for older firmware)",
|
||||
"M1002 judge_flag timelapse_record_flag ; Check if timelapse recording is needed",
|
||||
"M622 J1 ; Enable timelapse",
|
||||
" M400 ; Wait for all motions to complete",
|
||||
" M991 S0 P-1 ; End smooth timelapse at safe position",
|
||||
" M400 S3 ; Wait for last picture to be taken",
|
||||
"M623 ; End of \"timelapse_record_flag\"",
|
||||
"M400 ; Wait for all motions to complete",
|
||||
"M17 S ; Engage stepper motors",
|
||||
"M17 Z0.4 ; Lower Z motor current to reduce impact if obstruction exists",
|
||||
"G1 Z{z_max} F600 ; Drop bed",
|
||||
"M400 P100 ; Wait for 100ms",
|
||||
"M17 R ; Restore Z motor current",
|
||||
"M220 S100 ; Reset feedrate magnitude to 100%",
|
||||
"M201.2 K1.0 ; Reset acceleration magnitude",
|
||||
"M73.2 R1.0 ; Reset remaining time magnitude",
|
||||
"M1002 set_gcode_claim_speed_level : 0 ; Set G-code claim speed level to 0",
|
||||
"M17 X0.8 Y0.8 Z0.5 ; Lower motor current to 45% power",
|
||||
"M73 P100 R0 ; Set progress to 100% with 0 remaining time"
|
||||
],
|
||||
"gcodeProc": "",
|
||||
"gcodeFExt": "gcode",
|
||||
"extruders": [
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [
|
||||
"; BEGIN tool change from {last_tool} to {tool}",
|
||||
"M620 S{tool}A ; prepare tool change",
|
||||
"M204 S9000 ; Set acceleration to 9000 mm/s^2",
|
||||
"M620.11 S0 ; Reset filament retraction state",
|
||||
"M400 ; wait for moves to complete",
|
||||
"M620.1 E F187 T240 ; set purge rate 187 temp 240",
|
||||
"T{tool} ; initiate tool change",
|
||||
"M620.1 E F374 T240 ; set purge rate 374 temp 240",
|
||||
"M620.11 S0 ; Reset filament retraction state",
|
||||
"G92 E0 ; reset extruder position",
|
||||
"M400 ; wait for moves to complete",
|
||||
" ; -- PURGE --",
|
||||
"M109 S240 ; Set nozzle temperature to 240°C",
|
||||
"G1 E23.7 F187 ; Extrude 23.7mm of filament at flowrate 187",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F187 ; Continue extrusion at flowrate 187",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F374 ; Continue extrusion at flowrate 374",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F374 ; Continue extrusion at flowrate 374",
|
||||
"G1 E0.914611 F50 ; Pulsatile extrusion",
|
||||
"G1 E10.518 F374 ; Continue extrusion at flowrate 374",
|
||||
"G1 E-2 F1800 ; Retract filament by 2mm",
|
||||
"G1 E2 F300 ; Recover retraction slowly",
|
||||
"M400 ; Wait for all movements to complete",
|
||||
"M109 S{temp} ; Set nozzle temperature back to print temp",
|
||||
" ; -- SHAKE --",
|
||||
"M106 P1 S255 ; Turn on nozzle cooling fan to full speed",
|
||||
"M400 S3 ; Wait for all movements to complete",
|
||||
"G1 X70 F5000 ; Move to X70",
|
||||
"G1 X90 F3000 ; Move to X90",
|
||||
"G1 Y255 F4000 ; Move to Y255",
|
||||
"G1 X105 F5000 ; Move to X105",
|
||||
"G1 Y265 ; Move to Y265",
|
||||
"G1 X70 F10000 ; Rapid move to X70",
|
||||
"G1 X100 F5000 ; Move to X100",
|
||||
"G1 X70 F10000 ; Rapid move back to X70",
|
||||
"G1 X100 F5000 ; Move to X100",
|
||||
"G1 X70 F10000 ; Shake filament residue",
|
||||
"G1 X80 F15000",
|
||||
"G1 X60",
|
||||
"G1 X80",
|
||||
"G1 X60",
|
||||
"G1 X80 ; Shake to put down garbage",
|
||||
"G1 X100 F5000 ; Move to X100",
|
||||
"G1 X165 F15000 ; Wipe and shake",
|
||||
"G1 Y256 ; Move Y aside to prevent collision",
|
||||
"M400 ; Wait for all movements to complete",
|
||||
"M204 S10000 ; Set acceleration to 10000 mm/s^2",
|
||||
"M621 S{tool}A ; complete tool change",
|
||||
"; END tool change"
|
||||
],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
},
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
},
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
},
|
||||
{
|
||||
"extFilament": 1.75,
|
||||
"extNozzle": 0.4,
|
||||
"extSelect": [],
|
||||
"extOffsetX": 0,
|
||||
"extOffsetY": 0,
|
||||
"extDeselect": []
|
||||
}
|
||||
],
|
||||
"new": false,
|
||||
"deviceName": "Bambu P1S",
|
||||
"bedBelt": false,
|
||||
"fwRetract": false,
|
||||
"filamentSource": "direct",
|
||||
"extras": {
|
||||
"bbl": {}
|
||||
},
|
||||
"imageURL": "",
|
||||
"deviceZMax": 0,
|
||||
"gcodeTime": 1,
|
||||
"gcodeFeature": [],
|
||||
"profiles": [
|
||||
{
|
||||
"processName": "Bambu PLA",
|
||||
"sliceHeight": 0.2,
|
||||
"sliceShells": 2,
|
||||
"sliceShellOrder": "in-out",
|
||||
"sliceLayerStart": "last",
|
||||
"sliceFillAngle": 45,
|
||||
"sliceFillOverlap": 0.35,
|
||||
"sliceFillSparse": 0.1,
|
||||
"sliceFillType": "hex",
|
||||
"sliceAdaptive": false,
|
||||
"sliceMinHeight": 0,
|
||||
"sliceSupportDensity": 0.2,
|
||||
"sliceSupportOffset": 0.4,
|
||||
"sliceSupportGap": 1,
|
||||
"sliceSupportSize": 5,
|
||||
"sliceSupportArea": 0.25,
|
||||
"sliceSupportExtra": 0,
|
||||
"sliceSupportAngle": 60,
|
||||
"sliceSupportNozzle": 0,
|
||||
"sliceSolidMinArea": 0,
|
||||
"sliceBottomLayers": 2,
|
||||
"sliceTopLayers": 3,
|
||||
"firstLayerRate": 20,
|
||||
"firstLayerPrintMult": 1,
|
||||
"firstLayerYOffset": 0,
|
||||
"firstLayerBrim": 0,
|
||||
"firstLayerBeltLead": 0,
|
||||
"firstLayerFanSpeed": 0,
|
||||
"outputTemp": 210,
|
||||
"outputBedTemp": 60,
|
||||
"outputFanSpeed": 255,
|
||||
"outputFeedrate": 110,
|
||||
"outputFinishrate": 90,
|
||||
"outputSeekrate": 200,
|
||||
"outputShellMult": 1.2,
|
||||
"outputFillMult": 1.2,
|
||||
"outputSparseMult": 1.2,
|
||||
"outputRetractDist": 1,
|
||||
"outputRetractSpeed": 80,
|
||||
"outputRetractWipe": 0,
|
||||
"outputRetractDwell": 0,
|
||||
"outputShortPoly": 50,
|
||||
"outputMinSpeed": 5,
|
||||
"outputCoastDist": 0,
|
||||
"outputLayerRetract": false,
|
||||
"zHopDistance": 0,
|
||||
"antiBacklash": 0,
|
||||
"sliceFillWidth": 1,
|
||||
"sliceFillRate": 0,
|
||||
"sliceSupportEnable": false,
|
||||
"firstSliceHeight": 0.3,
|
||||
"firstLayerFillRate": 80,
|
||||
"firstLayerLineMult": 1,
|
||||
"firstLayerNozzleTemp": 220,
|
||||
"firstLayerBedTemp": 65,
|
||||
"firstLayerBrimTrig": 0,
|
||||
"firstLayerBrimGap": 0,
|
||||
"outputRaft": false,
|
||||
"outputRaftSpacing": 0.2,
|
||||
"outputBrimCount": 0,
|
||||
"outputBrimOffset": 2,
|
||||
"outputPurgeTower": 0,
|
||||
"outputInvertX": false,
|
||||
"outputInvertY": false,
|
||||
"arcTolerance": 0,
|
||||
"ranges": [],
|
||||
"sliceLineWidth": 0,
|
||||
"sliceFillRepeat": 2,
|
||||
"firstLayerBrimIn": 0,
|
||||
"firstLayerBeltBump": 0,
|
||||
"outputBeltFirst": false,
|
||||
"outputLoops": 0,
|
||||
"sliceFillGrow": 0,
|
||||
"sliceSolidRate": 0,
|
||||
"sliceSupportSpan": 5,
|
||||
"sliceSupportOutline": false,
|
||||
"firstLayerFlatten": 0,
|
||||
"outputDraftShield": false,
|
||||
"outputAvoidGaps": true,
|
||||
"sliceDetectThin": "off",
|
||||
"outputAlternating": false,
|
||||
"sliceLayerStartX": 0,
|
||||
"sliceLayerStartY": 0,
|
||||
"sliceSupportGrow": 0,
|
||||
"outputFanLayer": 1,
|
||||
"outputNozzle": 0,
|
||||
"sliceAngle": 45,
|
||||
"sliceZInterleave": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -27,6 +27,8 @@
|
|||
"G28 W ; home all without mesh bed level",
|
||||
"G92 E0.0",
|
||||
"G1 F{600} ",
|
||||
"M425 X0 Y0 Z0 ; Set backlash to specific values for all axis",
|
||||
"M425 F1 S3 ; Enable backlash compensation at 100% for 3 mm",
|
||||
"M117 ULIO 3D BRIX Printing"
|
||||
],
|
||||
"gcodePost": [
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
"G28 W ; home all without mesh bed level",
|
||||
"G92 E0.0",
|
||||
"G1 F{600} ",
|
||||
"M425 X0 Y0 Z0 ; Set backlash to specific values for all axis",
|
||||
"M425 F1 S3 ; Enable backlash compensation at 100% for 3 mm",
|
||||
"M117 ULIO 3D STEAM Printing"
|
||||
],
|
||||
"gcodePost": [
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
// dep: kiri-mode.cam.driver
|
||||
// use: kiri-mode.cam.animate
|
||||
// use: kiri-mode.cam.animate2
|
||||
// use: kiri-mode.cam.tools
|
||||
// use: load.gbr
|
||||
gapp.register("kiri-mode.cam.client", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { newPoint, newPolygon } = base;
|
||||
const { driver } = kiri;
|
||||
const { CAM } = driver;
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
|
@ -23,6 +23,7 @@ let isAnimate,
|
|||
isIndexed,
|
||||
isParsed,
|
||||
camStock,
|
||||
camZTop,
|
||||
camZBottom,
|
||||
current,
|
||||
currentIndex,
|
||||
|
|
@ -508,7 +509,7 @@ CAM.init = function(kiri, api) {
|
|||
let clazz = notime ? [ "draggable", "notime" ] : [ "draggable" ];
|
||||
let notable = rec.note ? rec.note.split(' ').filter(v => v.charAt(0) === '#') : undefined;
|
||||
if (clock) { clazz.push('clock'); title = ` title="end of ops chain\ndrag/drop like an op\nops after this are disabled"` }
|
||||
if (notable?.length) label = notable[0].slice(1);
|
||||
if (notable?.length) label += ` (${notable[0].slice(1)})`;
|
||||
html.appendAll([
|
||||
`<div id="${mark+i}" class="${clazz.join(' ')}"${title}>`,
|
||||
`<label class="label">${label}</label>`,
|
||||
|
|
@ -610,6 +611,10 @@ CAM.init = function(kiri, api) {
|
|||
}, 250);
|
||||
}
|
||||
function onDown(ev) {
|
||||
if (!ev.target.rec) {
|
||||
// only trigger on operation buttons bound to recs
|
||||
return;
|
||||
}
|
||||
let mobile = ev.touches;
|
||||
func.surfaceDone();
|
||||
func.traceDone();
|
||||
|
|
@ -930,6 +935,9 @@ CAM.init = function(kiri, api) {
|
|||
// SURFACE FUNCS
|
||||
let surfaceOn = false, lastWidget;
|
||||
func.surfaceAdd = (ev) => {
|
||||
if (surfaceOn) {
|
||||
return func.surfaceDone();
|
||||
}
|
||||
func.clearPops();
|
||||
alert = api.show.alert("analyzing surfaces...", 1000);
|
||||
let surfaces = poppedRec.surfaces;
|
||||
|
|
@ -981,6 +989,9 @@ CAM.init = function(kiri, api) {
|
|||
// TRACE FUNCS
|
||||
let traceOn = false, lastTrace;
|
||||
func.traceAdd = (ev) => {
|
||||
if (traceOn) {
|
||||
return func.traceDone();
|
||||
}
|
||||
func.clearPops();
|
||||
alert = api.show.alert("analyzing parts...", 1000);
|
||||
traceOn = hoveredOp;
|
||||
|
|
@ -1281,6 +1292,10 @@ CAM.init = function(kiri, api) {
|
|||
return current.device.spindleMax > 0;
|
||||
}
|
||||
|
||||
function zTop() {
|
||||
return API.conf.get().process.camZTop > 0;
|
||||
}
|
||||
|
||||
function zBottom() {
|
||||
return API.conf.get().process.camZBottom > 0;
|
||||
}
|
||||
|
|
@ -1318,7 +1333,9 @@ CAM.init = function(kiri, api) {
|
|||
voids: 'camRoughVoid',
|
||||
flats: 'camRoughFlat',
|
||||
inside: 'camRoughIn',
|
||||
top: 'camRoughTop'
|
||||
ov_topz: 0,
|
||||
ov_botz: 0,
|
||||
ov_conv: '~camConventional',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
|
|
@ -1334,8 +1351,13 @@ CAM.init = function(kiri, api) {
|
|||
all: UC.newBoolean(LANG.cr_clst_s, undefined, {title:LANG.cr_clst_l, show:hasIndexing}),
|
||||
voids: UC.newBoolean(LANG.cr_clrp_s, undefined, {title:LANG.cr_clrp_l}),
|
||||
flats: UC.newBoolean(LANG.cr_clrf_s, undefined, {title:LANG.cr_clrf_l}),
|
||||
top: UC.newBoolean(LANG.cr_clrt_s, undefined, {title:LANG.cr_clrt_l}),
|
||||
inside: UC.newBoolean(LANG.cr_olin_s, undefined, {title:LANG.cr_olin_l})
|
||||
inside: UC.newBoolean(LANG.cr_olin_s, undefined, {title:LANG.cr_olin_l}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
exp: UC.newExpand("overrides"),
|
||||
ov_topz: UC.newInput(LANG.ou_ztop_s, {title:LANG.ou_ztop_l, convert:UC.toFloat, units:true}),
|
||||
ov_botz: UC.newInput(LANG.ou_zbot_s, {title:LANG.ou_zbot_l, convert:UC.toFloat, units:true}),
|
||||
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, {title:LANG.ou_conv_l}),
|
||||
exp_end: UC.endExpand(),
|
||||
};
|
||||
|
||||
createPopOp('outline', {
|
||||
|
|
@ -1352,7 +1374,10 @@ CAM.init = function(kiri, api) {
|
|||
outside: 'camOutlineOut',
|
||||
inside: 'camOutlineIn',
|
||||
wide: 'camOutlineWide',
|
||||
top: 'camOutlineTop'
|
||||
top: 'camOutlineTop',
|
||||
ov_topz: 0,
|
||||
ov_botz: 0,
|
||||
ov_conv: '~camConventional',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
|
|
@ -1371,6 +1396,12 @@ CAM.init = function(kiri, api) {
|
|||
omitvoid: UC.newBoolean(LANG.co_omvd_s, undefined, {title:LANG.co_omvd_l, xshow:(op) => { return op.inputs.outside.checked }}),
|
||||
wide: UC.newBoolean(LANG.co_wide_s, undefined, {title:LANG.co_wide_l, show:(op) => { return !op.inputs.inside.checked }}),
|
||||
dogbones: UC.newBoolean(LANG.co_dogb_s, undefined, {title:LANG.co_dogb_l, show:(op) => { return !op.inputs.wide.checked }}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
exp: UC.newExpand("overrides"),
|
||||
ov_topz: UC.newInput(LANG.ou_ztop_s, {title:LANG.ou_ztop_l, convert:UC.toFloat, units:true}),
|
||||
ov_botz: UC.newInput(LANG.ou_zbot_s, {title:LANG.ou_zbot_l, convert:UC.toFloat, units:true}),
|
||||
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, {title:LANG.ou_conv_l}),
|
||||
exp_end: UC.endExpand(),
|
||||
};
|
||||
|
||||
const contourFilter = gcodeEditor('Layer Filter', 'filter');
|
||||
|
|
@ -1422,7 +1453,7 @@ CAM.init = function(kiri, api) {
|
|||
tolerance: 'camTolerance',
|
||||
filter: 'camContourFilter',
|
||||
leave: 'camContourLeave',
|
||||
axis: 'X'
|
||||
linear: 'camLatheLinear'
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
// axis: UC.newSelect(LANG.cd_axis, {}, "xyaxis"),
|
||||
|
|
@ -1435,6 +1466,8 @@ CAM.init = function(kiri, api) {
|
|||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
tolerance: UC.newInput(LANG.ou_toll_s, {title:LANG.ou_toll_l, convert:UC.toFloat, bound:UC.bound(0,10.0), units:true, round:4}),
|
||||
leave: UC.newInput(LANG.cf_leav_s, {title:LANG.cf_leav_l, convert:UC.toFloat, bound:UC.bound(0,100)}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
linear: UC.newBoolean(LANG.ci_line_s, undefined, {title:LANG.ci_line_l}),
|
||||
// filter: UC.newRow([ UC.newButton(LANG.filter, contourFilter) ], {class:"ext-buttons f-row"})
|
||||
};
|
||||
|
||||
|
|
@ -1461,11 +1494,14 @@ CAM.init = function(kiri, api) {
|
|||
thru: 'camTraceThru',
|
||||
rate: 'camTraceSpeed',
|
||||
plunge: 'camTracePlunge',
|
||||
bottom: 'camTraceBottom',
|
||||
offover: 'camTraceOffOver',
|
||||
dogbone: 'camTraceDogbone',
|
||||
revbone: 'camTraceDogbone',
|
||||
select: 'camTraceMode'
|
||||
merge: 'camTraceMerge',
|
||||
select: 'camTraceMode',
|
||||
ov_topz: 0,
|
||||
ov_botz: 0,
|
||||
ov_conv: '~camConventional',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
select: UC.newSelect(LANG.cc_sele_s, {title:LANG.cc_sele_l}, "select"),
|
||||
|
|
@ -1481,14 +1517,17 @@ CAM.init = function(kiri, api) {
|
|||
thru: UC.newInput(LANG.cc_thru_s, {title:LANG.cc_thru_l, convert:UC.toFloat, units:true}),
|
||||
offover: UC.newInput(LANG.cc_offd_s, {title:LANG.cc_offd_l, convert:UC.toFloat, units:true, show:() => poppedRec.offset !== "none"}),
|
||||
sep: UC.newBlank({class:"pop-sep", modes:MCAM, xshow:zDogSep}),
|
||||
bottom: UC.newBoolean(LANG.cf_botm_s, undefined, {title:LANG.cf_botm_l, show:(op,conf) => conf.process.camZBottom}),
|
||||
merge: UC.newBoolean(LANG.co_merg_s, undefined, {title:LANG.co_merg_l, show:() => !popOp.trace.rec.down}),
|
||||
dogbone: UC.newBoolean(LANG.co_dogb_s, undefined, {title:LANG.co_dogb_l, show:canDogBones}),
|
||||
revbone: UC.newBoolean(LANG.co_dogr_s, undefined, {title:LANG.co_dogr_l, show:canDogBonesRev}),
|
||||
exp: UC.newExpand("overrides"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
menu: UC.newRow([
|
||||
UC.newButton(undefined, func.traceAdd, {icon:'<i class="fas fa-plus"></i>'}),
|
||||
UC.newButton(undefined, func.traceDone, {icon:'<i class="fas fa-check"></i>'}),
|
||||
], {class:"ext-buttons f-row"}),
|
||||
ov_topz: UC.newInput(LANG.ou_ztop_s, {title:LANG.ou_ztop_l, convert:UC.toFloat, units:true}),
|
||||
ov_botz: UC.newInput(LANG.ou_zbot_s, {title:LANG.ou_zbot_l, convert:UC.toFloat, units:true}),
|
||||
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, {title:LANG.ou_conv_l}),
|
||||
exp_end: UC.endExpand(),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
menu: UC.newRow([ UC.newButton("select", func.traceAdd) ], {class:"ext-buttons f-row"}),
|
||||
};
|
||||
|
||||
createPopOp('pocket', {
|
||||
|
|
@ -1505,6 +1544,9 @@ CAM.init = function(kiri, api) {
|
|||
contour: 'camPocketContour',
|
||||
engrave: 'camPocketEngrave',
|
||||
outline: 'camPocketOutline',
|
||||
ov_topz: 0,
|
||||
ov_botz: 0,
|
||||
ov_conv: '~camConventional',
|
||||
tolerance: 'camTolerance',
|
||||
}).inputs = {
|
||||
tool: UC.newSelect(LANG.cc_tool, {}, "tools"),
|
||||
|
|
@ -1525,11 +1567,14 @@ CAM.init = function(kiri, api) {
|
|||
contour: UC.newBoolean(LANG.cp_cont_s, undefined, {title:LANG.cp_cont_s}),
|
||||
outline: UC.newBoolean(LANG.cp_outl_s, undefined, {title:LANG.cp_outl_l, show:() => !poppedRec.contour}),
|
||||
engrave: UC.newBoolean(LANG.cp_engr_s, undefined, {title:LANG.cp_engr_l, show:() => poppedRec.contour}),
|
||||
exp: UC.newExpand("overrides"),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
menu: UC.newRow([
|
||||
UC.newButton(undefined, func.surfaceAdd, {icon:'<i class="fas fa-plus"></i>'}),
|
||||
UC.newButton(undefined, func.surfaceDone, {icon:'<i class="fas fa-check"></i>'}),
|
||||
], {class:"ext-buttons f-row"}),
|
||||
ov_topz: UC.newInput(LANG.ou_ztop_s, {title:LANG.ou_ztop_l, convert:UC.toFloat, units:true}),
|
||||
ov_botz: UC.newInput(LANG.ou_zbot_s, {title:LANG.ou_zbot_l, convert:UC.toFloat, units:true}),
|
||||
ov_conv: UC.newBoolean(LANG.ou_conv_s, undefined, {title:LANG.ou_conv_l}),
|
||||
exp_end: UC.endExpand(),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
menu: UC.newRow([ UC.newButton("select", func.surfaceAdd) ], {class:"ext-buttons f-row"}),
|
||||
};
|
||||
|
||||
createPopOp('drill', {
|
||||
|
|
@ -1584,9 +1629,7 @@ CAM.init = function(kiri, api) {
|
|||
sep: UC.newBlank({class:"pop-sep", modes:MCAM, show:zBottom}),
|
||||
invert: UC.newBoolean(LANG.cf_nvrt_s, undefined, {title:LANG.cf_nvrt_l, show:zBottom}),
|
||||
sep: UC.newBlank({class:"pop-sep"}),
|
||||
action: UC.newRow([
|
||||
UC.newButton(LANG.cf_menu, func.opFlip)
|
||||
], {class:"ext-buttons f-row"})
|
||||
action: UC.newRow([ UC.newButton(LANG.cf_menu, func.opFlip) ], {class:"ext-buttons f-row"})
|
||||
};
|
||||
|
||||
createPopOp('gcode', {
|
||||
|
|
@ -1669,8 +1712,16 @@ function createPopOp(type, map) {
|
|||
for (let [key, val] of Object.entries(op.inputs)) {
|
||||
let type = val.type;
|
||||
let from = map[key];
|
||||
if (rec[key] === undefined && type && from) {
|
||||
rec[key] = current.process[from];
|
||||
let rval = rec[key];
|
||||
// fill undef entries older defines
|
||||
if (type && (rval === null || rval === undefined)) {
|
||||
if (typeof(from) === 'string') {
|
||||
rec[key] = current.process[from];
|
||||
} else if (from !== undefined) {
|
||||
rec[key] = from;
|
||||
} else {
|
||||
console.log('error', { key, val, type, from });
|
||||
}
|
||||
}
|
||||
}
|
||||
API.util.rec2ui(rec, op.inputs);
|
||||
|
|
@ -1683,7 +1734,7 @@ function createPopOp(type, map) {
|
|||
API.util.ui2rec(op.rec, op.inputs);
|
||||
for (let [key, val] of Object.entries(op.rec)) {
|
||||
let saveTo = map[key];
|
||||
if (saveTo) {
|
||||
if (saveTo && typeof(key) === 'string' && !key.startsWith("~")) {
|
||||
current.process[saveTo] = val;
|
||||
}
|
||||
}
|
||||
|
|
@ -1692,15 +1743,17 @@ function createPopOp(type, map) {
|
|||
},
|
||||
new: () => {
|
||||
let rec = { type };
|
||||
for (let [key, val] of Object.entries(map)) {
|
||||
rec[key] = current.process[val];
|
||||
for (let [key, src] of Object.entries(map)) {
|
||||
rec[key] = typeof(src) === 'string'
|
||||
? current.process[src.replace('~','')]
|
||||
: src;
|
||||
}
|
||||
return rec;
|
||||
},
|
||||
hideshow: (abc) => {
|
||||
hideshow: () => {
|
||||
for (let inp of Object.values(op.inputs)) {
|
||||
let parent = inp.parentElement;
|
||||
if (parent.setVisible && parent.__opt.show) {
|
||||
if (parent && parent.setVisible && parent.__opt.show) {
|
||||
parent.setVisible(parent.__opt.show(op, API.conf.get()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1872,9 +1925,11 @@ function updateStock() {
|
|||
}
|
||||
|
||||
if (!isCamMode) {
|
||||
SPACE.world.remove(camZTop);
|
||||
SPACE.world.remove(camZBottom);
|
||||
SPACE.world.remove(camStock);
|
||||
camStock = null;
|
||||
camZTop = null;
|
||||
camZBottom = null;
|
||||
return;
|
||||
}
|
||||
|
|
@ -1937,6 +1992,32 @@ function updateStock() {
|
|||
camStock = null;
|
||||
}
|
||||
|
||||
SPACE.world.remove(camZTop);
|
||||
if (process.camZTop && widgets.length) {
|
||||
let max = { x, y, z };
|
||||
for (let w of widgets) {
|
||||
max.x = Math.max(max.x, w.track.box.w);
|
||||
max.y = Math.max(max.y, w.track.box.h);
|
||||
max.z = Math.max(max.z, w.track.box.d);
|
||||
}
|
||||
let geo = new THREE.PlaneGeometry(max.x, max.y);
|
||||
let mat = new THREE.MeshBasicMaterial({
|
||||
color: 0x777777,
|
||||
opacity: 0.55,
|
||||
transparent: true,
|
||||
side:THREE.DoubleSide
|
||||
});
|
||||
camZTop = new THREE.Mesh(geo, mat);
|
||||
camZTop._max = max;
|
||||
camZTop.renderOrder = 1;
|
||||
camZTop.position.x = center.x;
|
||||
camZTop.position.y = center.y;
|
||||
camZTop.position.z = process.camZTop;
|
||||
SPACE.world.add(camZTop);
|
||||
} else {
|
||||
camZTop = undefined;
|
||||
}
|
||||
|
||||
SPACE.world.remove(camZBottom);
|
||||
if (process.camZBottom && widgets.length) {
|
||||
let max = { x, y, z };
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// dep: geo.base
|
||||
// dep: geo.polygons
|
||||
// dep: kiri-mode.cam.driver
|
||||
gapp.register("kiri-mode.cam.export", [], (root, exports) => {
|
||||
gapp.register("kiri-mode.cam.export", (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { polygons, util } = base;
|
||||
|
|
@ -70,7 +70,8 @@ CAM.export = function(print, online) {
|
|||
},
|
||||
offset = {
|
||||
x: -origin.x,
|
||||
y: origin.y
|
||||
y: origin.y,
|
||||
z: spro.camOriginTop ? origin.z - zmax : origin.z
|
||||
},
|
||||
scale = {
|
||||
x: 1,
|
||||
|
|
@ -88,7 +89,7 @@ CAM.export = function(print, online) {
|
|||
time_sec: 0,
|
||||
time_ms: 0,
|
||||
time: 0
|
||||
}
|
||||
};
|
||||
|
||||
function section(section) {
|
||||
append();
|
||||
|
|
@ -197,10 +198,14 @@ CAM.export = function(print, online) {
|
|||
}
|
||||
}
|
||||
|
||||
// enforce XY origin at start of print
|
||||
if (points === 0 || changeTool) {
|
||||
pos.x = pos.y = pos.z = 0;
|
||||
}
|
||||
|
||||
// split first move to X,Y then Z for that new location
|
||||
// safety to prevent tool crashing
|
||||
if (points === 0) {
|
||||
pos.x = pos.y = pos.z = 0;
|
||||
if (points === 0 || changeTool) {
|
||||
points++;
|
||||
if (spro.camFirstZMax) {
|
||||
moveTo({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
// dep: kiri.slice
|
||||
// use: kiri-mode.cam.topo
|
||||
// use: kiri-mode.cam.topo4
|
||||
gapp.register("kiri-mode.cam.ops", [], (root, exports) => {
|
||||
gapp.register("kiri-mode.cam.ops", (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { paths, polygons, newPoint, newPolygon, sliceConnect } = base;
|
||||
|
|
@ -155,7 +155,7 @@ class OpLevel extends CamOp {
|
|||
poly.reverse();
|
||||
}
|
||||
poly.forEachPoint((point, pidx) => {
|
||||
camOut(point.clone(), pidx > 0, stepOver);
|
||||
camOut(point.clone(), true, stepOver);
|
||||
}, false);
|
||||
});
|
||||
setPrintPoint(printPoint);
|
||||
|
|
@ -171,9 +171,10 @@ class OpRough extends CamOp {
|
|||
|
||||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, slicer, addSlices, unsafe, color } = state;
|
||||
let { settings, slicer, addSlices, unsafe, color } = state;
|
||||
let { updateToolDiams, thruHoles, tabs, cutTabs, cutPolys } = state;
|
||||
let { tshadow, shadowTop, ztOff, zBottom, zThru, zMax, shadowAt, isIndexed } = state;
|
||||
let { ztOff, zMax, shadowAt, isIndexed} = state;
|
||||
let { workarea } = state;
|
||||
let { process, stock } = settings;
|
||||
|
||||
if (op.down <= 0) {
|
||||
|
|
@ -181,7 +182,6 @@ class OpRough extends CamOp {
|
|||
}
|
||||
|
||||
let roughIn = op.inside;
|
||||
let roughTop = op.top;
|
||||
let roughDown = op.down;
|
||||
let roughLeave = op.leave || 0;
|
||||
let roughLeaveZ = op.leavez || 0;
|
||||
|
|
@ -189,9 +189,11 @@ class OpRough extends CamOp {
|
|||
let toolDiam = new CAM.Tool(settings, op.tool).fluteDiameter();
|
||||
let trueShadow = process.camTrueShadow === true;
|
||||
|
||||
// create facing slices
|
||||
if (roughTop) {
|
||||
let shadow = tshadow.clone();
|
||||
updateToolDiams(toolDiam);
|
||||
|
||||
// clear the stock above the area to be roughed out
|
||||
if (workarea.top_z > workarea.top_part) {
|
||||
let shadow = state.shadow.base.clone();
|
||||
let step = toolDiam * op.step;
|
||||
let inset = roughStock ?
|
||||
POLY.offset([ newPolygon().centerRectangle(stock.center, stock.x, stock.y) ], step) :
|
||||
|
|
@ -210,7 +212,7 @@ class OpRough extends CamOp {
|
|||
let camFaces = this.camFaces = [];
|
||||
let zstart = zMax + ztOff - zstep;
|
||||
for (let z = zstart; zsteps > 0; zsteps--) {
|
||||
let slice = shadowTop.slice.clone(false);
|
||||
let slice = newSlice();
|
||||
slice.z = z;
|
||||
slice.camLines = POLY.setZ(facing.clone(true), slice.z + roughLeaveZ);
|
||||
slice.output()
|
||||
|
|
@ -223,23 +225,24 @@ class OpRough extends CamOp {
|
|||
}
|
||||
|
||||
// create roughing slices
|
||||
updateToolDiams(toolDiam);
|
||||
|
||||
let flats = [];
|
||||
let shadow = [];
|
||||
let slices = [];
|
||||
let indices = slicer.interval(roughDown, {
|
||||
down: true, min: 0, fit: true, off: 0.01
|
||||
});
|
||||
|
||||
// shift out first (top-most) slice
|
||||
indices.shift();
|
||||
|
||||
// find flats and add to indices for slicing
|
||||
if (op.flats) {
|
||||
let flatArea = (Math.PI * (toolDiam/2) * (toolDiam/2)) / 2;
|
||||
let flats = Object.entries(slicer.zFlat)
|
||||
.filter(row => row[1] > flatArea)
|
||||
.map(row => row[0])
|
||||
.map(v => parseFloat(v).round(5))
|
||||
.filter(v => v >= zBottom);
|
||||
.filter(v => v >= workarea.bottom_z);
|
||||
flats.forEach(v => {
|
||||
if (!indices.contains(v)) {
|
||||
indices.push(v);
|
||||
|
|
@ -267,7 +270,7 @@ class OpRough extends CamOp {
|
|||
indices = indices.appendAll(flats).sort((a,b) => b-a);
|
||||
}
|
||||
|
||||
indices = indices.filter(v => v >= zBottom);
|
||||
indices = indices.filter(v => v >= workarea.bottom_z);
|
||||
// console.log('indices', ...indices, {zBottom});
|
||||
|
||||
let cnt = 0;
|
||||
|
|
@ -278,11 +281,11 @@ class OpRough extends CamOp {
|
|||
// exclude flats injected to complete shadow
|
||||
return;
|
||||
}
|
||||
// data.shadow = trueShadow ? CAM.shadowAt(widget, data.z) : shadow.clone(true);
|
||||
if (data.z > workarea.top_z) {
|
||||
return;
|
||||
}
|
||||
data.shadow = trueShadow ? shadowAt(data.z) : shadow.clone(true);
|
||||
data.slice.shadow = data.shadow;
|
||||
// data.slice.tops[0].inner = data.shadow;
|
||||
// data.slice.tops[0].inner = POLY.setZ(tshadow.clone(true), data.z);
|
||||
slices.push(data.slice);
|
||||
progress(0.25 + 0.25 * (++cnt / tot));
|
||||
}, progress: (index, total) => {
|
||||
|
|
@ -291,9 +294,9 @@ class OpRough extends CamOp {
|
|||
} });
|
||||
|
||||
if (trueShadow) {
|
||||
shadow = tshadow.clone(true);
|
||||
shadow = state.shadow.base.clone(true);
|
||||
} else {
|
||||
shadow = POLY.union(shadow.appendAll(shadowTop.tops), 0.01, true);
|
||||
shadow = POLY.union(shadow.appendAll(state.shadow.base), 0.01, true);
|
||||
}
|
||||
|
||||
// inset or eliminate thru holes from shadow
|
||||
|
|
@ -301,10 +304,6 @@ class OpRough extends CamOp {
|
|||
thruHoles.forEach(hole => {
|
||||
shadow = shadow.map(p => {
|
||||
if (p.isEquivalent(hole)) {
|
||||
// eliminate thru holes when roughing voids enabled
|
||||
// if (op.voids) {
|
||||
// return undefined;
|
||||
// }
|
||||
let po = POLY.offset([p], -(toolDiam / 2 + roughLeave + 0.01));
|
||||
return po ? po[0] : undefined;
|
||||
} else {
|
||||
|
|
@ -314,6 +313,7 @@ class OpRough extends CamOp {
|
|||
});
|
||||
shadow = POLY.nest(shadow);
|
||||
if (op.voids) {
|
||||
// eliminate voids from shadow when "clear voids" enables
|
||||
for (let s of shadow) s.inner = undefined;
|
||||
}
|
||||
|
||||
|
|
@ -406,7 +406,9 @@ class OpRough extends CamOp {
|
|||
});
|
||||
|
||||
let last = slices[slices.length-1];
|
||||
for (let zneg of base.util.lerp(0, zThru, op.down)) {
|
||||
|
||||
if (workarea.bottom_z < 0)
|
||||
for (let zneg of base.util.lerp(0, -workarea.bottom_cut, op.down)) {
|
||||
if (!last) continue;
|
||||
let add = last.clone(true);
|
||||
add.z -= zneg;
|
||||
|
|
@ -435,13 +437,14 @@ class OpRough extends CamOp {
|
|||
let { process } = settings;
|
||||
|
||||
let easeDown = process.camEaseDown;
|
||||
let cutdir = process.camConventional;
|
||||
let cutdir = op.ov_conv;
|
||||
let depthFirst = process.camDepthFirst && !state.isIndexed;
|
||||
let depthData = [];
|
||||
|
||||
setTool(op.tool, op.rate, op.plunge);
|
||||
setSpindle(op.spindle);
|
||||
|
||||
// output the clearing of stock above roughing
|
||||
for (let slice of (camFaces || [])) {
|
||||
const level = [];
|
||||
for (let poly of slice.camLines) {
|
||||
|
|
@ -463,6 +466,7 @@ class OpRough extends CamOp {
|
|||
newLayer();
|
||||
}
|
||||
|
||||
// output the roughing passes
|
||||
setPrintPoint(printPoint);
|
||||
sliceOutput(sliceOut, {
|
||||
cutdir,
|
||||
|
|
@ -481,8 +485,8 @@ class OpOutline extends CamOp {
|
|||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { settings, widget, slicer, addSlices, tshadow, thruHoles, unsafe, color } = state;
|
||||
let { updateToolDiams, zThru, zBottom, shadowTop, tabs, cutTabs, cutPolys } = state;
|
||||
let { zMax, ztOff } = state;
|
||||
let { updateToolDiams, tabs, cutTabs, cutPolys, workarea } = state;
|
||||
let { zMax } = state;
|
||||
let { process, stock } = settings;
|
||||
|
||||
if (op.down <= 0) {
|
||||
|
|
@ -494,8 +498,11 @@ class OpOutline extends CamOp {
|
|||
let shadow = [];
|
||||
let slices = [];
|
||||
let intopt = {
|
||||
down: true, min: zBottom, fit: true, off: 0.01,
|
||||
max: op.top ? zMax + ztOff : undefined
|
||||
off: 0.01,
|
||||
fit: true,
|
||||
down: true,
|
||||
min: Math.max(0, workarea.bottom_z),
|
||||
max: workarea.top_z
|
||||
};
|
||||
let indices = slicer.interval(op.down, intopt);
|
||||
let trueShadow = process.camTrueShadow === true;
|
||||
|
|
@ -506,12 +513,11 @@ class OpOutline extends CamOp {
|
|||
.map(v => (parseFloat(v) - 0.01).round(5))
|
||||
.filter(v => v > 0 && indices.indexOf(v) < 0);
|
||||
indices = indices.appendAll(flats).sort((a,b) => b-a);
|
||||
indices = indices.filter(v => v - zBottom >= -0.001);
|
||||
// console.log('indices', ...indices, {zBottom, slicer});
|
||||
|
||||
let cnt = 0;
|
||||
let tot = 0;
|
||||
if (op.outside && !op.inside) {
|
||||
// console.log({outline_bypass: indices, down: op.down});
|
||||
console.log({outline_bypass: indices, down: op.down});
|
||||
indices.forEach((ind,i) => {
|
||||
if (flats.indexOf(ind) >= 0) {
|
||||
// exclude flats
|
||||
|
|
@ -540,13 +546,13 @@ class OpOutline extends CamOp {
|
|||
tot = total;
|
||||
progress((index / total) * 0.5);
|
||||
} });
|
||||
shadow = POLY.union(shadow.appendAll(shadowTop.tops), 0.01, true);
|
||||
shadow = POLY.union(shadow.appendAll(state.shadow.base), 0.01, true);
|
||||
|
||||
// start slices at top of stock when `clear top` enabled
|
||||
if (op.top) {
|
||||
let first = slices[0];
|
||||
let zlist = slices.map(s => s.z);
|
||||
for (let z of indices.filter(v => v >= zMax).reverse()) {
|
||||
for (let z of indices.filter(v => v >= zMax)) {
|
||||
if (zlist.contains(z)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -559,9 +565,9 @@ class OpOutline extends CamOp {
|
|||
}
|
||||
|
||||
// extend cut thru (only when z bottom is 0)
|
||||
if (zThru) {
|
||||
if (workarea.bottom_z < 0) {
|
||||
let last = slices[slices.length-1];
|
||||
for (let zneg of base.util.lerp(0, zThru, op.down)) {
|
||||
for (let zneg of base.util.lerp(0, -workarea.bottom_cut, op.down)) {
|
||||
if (!last) continue;
|
||||
let add = last.clone(true);
|
||||
add.tops.forEach(top => top.poly.setZ(add.z));
|
||||
|
|
@ -678,7 +684,7 @@ class OpOutline extends CamOp {
|
|||
|
||||
let easeDown = process.camEaseDown;
|
||||
let toolDiam = this.toolDiam;
|
||||
let cutdir = process.camConventional;
|
||||
let cutdir = op.ov_conv;//process.camConventional;
|
||||
let depthFirst = process.camDepthFirst;
|
||||
let depthData = [];
|
||||
|
||||
|
|
@ -948,25 +954,29 @@ class OpTrace extends CamOp {
|
|||
async slice(progress) {
|
||||
let { op, state } = this;
|
||||
let { tool, rate, down, plunge, offset, offover, thru } = op;
|
||||
let { settings, widget, addSlices, zMax, zTop, zThru, tabs } = state;
|
||||
let { ov_conv } = op;
|
||||
let { settings, widget, addSlices, zThru, tabs, workarea } = state;
|
||||
let { updateToolDiams, cutTabs, cutPolys, healPolys, color } = state;
|
||||
let { process, stock } = settings;
|
||||
let { camStockClipTo, camZBottom } = process;
|
||||
let { camStockClipTo } = process;
|
||||
if (state.isIndexed) {
|
||||
throw 'trace op not supported with indexed stock';
|
||||
}
|
||||
// generate tracing offsets from chosen features
|
||||
let zTop = workarea.top_z;
|
||||
let zBottom = workarea.bottom_z;
|
||||
let sliceOut = this.sliceOut = [];
|
||||
let areas = op.areas[widget.id] || [];
|
||||
let camTool = new CAM.Tool(settings, tool);
|
||||
let toolDiam = camTool.fluteDiameter();
|
||||
let toolOver = toolDiam * op.step;
|
||||
let traceOffset = camTool.traceOffset()
|
||||
let cutdir = process.camConventional;
|
||||
let cutdir = ov_conv;
|
||||
let polys = [];
|
||||
let stockRect = stock.center && stock.x && stock.y ?
|
||||
newPolygon().centerRectangle(stock.center, stock.x, stock.y) : undefined;
|
||||
updateToolDiams(toolDiam);
|
||||
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => {
|
||||
tab.off = POLY.expand([tab.poly], toolDiam / 2).flat();
|
||||
|
|
@ -986,6 +996,9 @@ class OpTrace extends CamOp {
|
|||
sliceOut.push(slice);
|
||||
return slice;
|
||||
}
|
||||
function minZ(z) {
|
||||
return zBottom ? Math.max(zBottom, z - thru) : z - thru;
|
||||
}
|
||||
function followZ(poly) {
|
||||
if (op.dogbone) {
|
||||
CAM.addDogbones(poly, toolDiam / 5, !op.revbone);
|
||||
|
|
@ -1002,7 +1015,7 @@ class OpTrace extends CamOp {
|
|||
slice.camLines = cutPolys([stockRect], slice.camLines, z, true);
|
||||
}
|
||||
slice.output()
|
||||
.setLayer("follow", {line: color}, false)
|
||||
.setLayer("trace follow", {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
}
|
||||
function clearZ(polys, z, down) {
|
||||
|
|
@ -1013,7 +1026,8 @@ class OpTrace extends CamOp {
|
|||
let slice = newSliceOut(z);
|
||||
slice.camTrace = { tool, rate, plunge };
|
||||
POLY.offset([ poly ], [ -toolDiam/2, -toolOver ], {
|
||||
count:999, outs: slice.camLines = [], flat:true, z
|
||||
count:999, outs: slice.camLines = [], flat:true, z,
|
||||
minArea: 0
|
||||
});
|
||||
if (tabs) {
|
||||
slice.camLines = cutTabs(tabs, POLY.flatten(slice.camLines, null, true), z);
|
||||
|
|
@ -1022,7 +1036,7 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
POLY.setWinding(slice.camLines, cutdir, false);
|
||||
slice.output()
|
||||
.setLayer("clear", {line: color}, false)
|
||||
.setLayer("trace clear", {line: color}, false)
|
||||
.addPolys(slice.camLines)
|
||||
}
|
||||
}
|
||||
|
|
@ -1089,6 +1103,7 @@ class OpTrace extends CamOp {
|
|||
poly2polyEmit(polys, newPoint(0,0,0), (poly, index, count, spoint) => {
|
||||
routed.push(poly);
|
||||
});
|
||||
let output = [];
|
||||
for (let poly of POLY.nest(routed)) {
|
||||
let offdist = offset !== 'none' ? offover : 0;
|
||||
if (!offdist)
|
||||
|
|
@ -1110,29 +1125,34 @@ class OpTrace extends CamOp {
|
|||
}
|
||||
for (let pi of POLY.flatten(poly, [], true))
|
||||
if (down) {
|
||||
let zto = pi.getZ() - thru;
|
||||
let zto = minZ(pi.getZ());
|
||||
if (zThru && similar(zto,0)) {
|
||||
zto -= zThru;
|
||||
}
|
||||
for (let z of base.util.lerp(zTop, zto, down)) {
|
||||
followZ(pi.clone().setZ(z));
|
||||
output.push(pi.clone().setZ(z));
|
||||
}
|
||||
} else {
|
||||
if (thru) {
|
||||
pi.setZ(pi.getZ() - thru);
|
||||
}
|
||||
followZ(pi);
|
||||
output.push(pi);
|
||||
}
|
||||
if (!down && op.merge) {
|
||||
let nest = POLY.nest(output);
|
||||
let union = POLY.union(nest, 0, true);
|
||||
output = POLY.flatten(union, [], true);
|
||||
}
|
||||
}
|
||||
for (let poly of output) {
|
||||
followZ(poly);
|
||||
}
|
||||
break;
|
||||
case "clear":
|
||||
const zbo = widget.track.top - widget.track.box.d;
|
||||
let zmap = {};
|
||||
for (let poly of polys) {
|
||||
let z = poly.getZ() - thru;
|
||||
if (op.bottom && camZBottom) {
|
||||
z = Math.max(z, camZBottom) - zbo;
|
||||
}
|
||||
let z = minZ(poly.getZ());
|
||||
if (offover) {
|
||||
let pnew = POLY.offset([poly], -offover, { minArea: 0, open: true });
|
||||
if (pnew) {
|
||||
|
|
@ -1174,17 +1194,20 @@ class OpPocket extends CamOp {
|
|||
const debug = false;
|
||||
let { op, state } = this;
|
||||
let { tool, rate, down, plunge, expand, contour, smooth, tolerance } = op;
|
||||
let { settings, widget, addSlices, zTop, zBottom, zThru, tabs, color } = state;
|
||||
let { updateToolDiams, cutTabs, cutPolys, healPolys, shadowAt } = state;
|
||||
let { process, stock } = settings;
|
||||
let { ov_botz, ov_conv } = op;
|
||||
let { settings, widget, addSlices, zBottom, zThru, tabs, color } = state;
|
||||
let { updateToolDiams, cutTabs, healPolys, shadowAt, workarea } = state;
|
||||
let { process } = settings;
|
||||
zBottom = ov_botz ? workarea.bottom_stock + ov_botz : zBottom;
|
||||
// generate tracing offsets from chosen features
|
||||
let sliceOut;
|
||||
let pockets = this.pockets = [];
|
||||
let camTool = new CAM.Tool(settings, tool);
|
||||
let toolDiam = camTool.fluteDiameter();
|
||||
let toolOver = toolDiam * op.step;
|
||||
let cutdir = process.camConventional;
|
||||
let cutdir = ov_conv;
|
||||
let engrave = contour && op.engrave;
|
||||
let zTop = workarea.top_z;
|
||||
if (contour) {
|
||||
down = 0;
|
||||
this.topo = await CAM.Topo({
|
||||
|
|
@ -1219,6 +1242,7 @@ class OpPocket extends CamOp {
|
|||
return slice;
|
||||
}
|
||||
function clearZ(polys, z, down) {
|
||||
// console.log({ clearZ: polys });
|
||||
if (down) {
|
||||
// adjust step down to a value <= down that
|
||||
// ends on the lowest z specified
|
||||
|
|
@ -1250,7 +1274,7 @@ class OpPocket extends CamOp {
|
|||
if (smooth) {
|
||||
shadow = POLY.setZ(POLY.offset(POLY.offset(shadow, smooth), -smooth), z);
|
||||
}
|
||||
POLY.subtract([ poly ], shadow, clip);
|
||||
POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0);
|
||||
}
|
||||
if (clip.length === 0) {
|
||||
continue;
|
||||
|
|
@ -1263,7 +1287,7 @@ class OpPocket extends CamOp {
|
|||
[ expand || (-0.02), -toolOver ] :
|
||||
[ -toolDiam / 2, -toolOver ];
|
||||
POLY.offset(clip, offs, {
|
||||
count, outs: slice.camLines = [], flat:true, z
|
||||
count, outs: slice.camLines = [], flat:true, z, minArea: 0
|
||||
});
|
||||
} else {
|
||||
// when engraving with a 0 width tip
|
||||
|
|
@ -1418,7 +1442,7 @@ class OpPocket extends CamOp {
|
|||
if (min.pocket) {
|
||||
min.pocket.used = true;
|
||||
sliceOutput(min.pocket, {
|
||||
cutdir: process.camConventional,
|
||||
cutdir: op.ov_conv,
|
||||
depthFirst: process.camDepthFirst && !state.isIndexed,
|
||||
easeDown: op.down && process.easeDown ? op.down : 0,
|
||||
progress: (n,m) => progress(n/m, "pocket")
|
||||
|
|
@ -1708,6 +1732,18 @@ class OpXRay extends CamOp {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the Part "shadow" and attaches relevant data to the "state" object
|
||||
*
|
||||
* The part shadow consists of top-cown layers at which the polygon shadow changes
|
||||
* shape. For curved or sloped surfaces, this is approximated and paths that clip
|
||||
* to it should use the next lower layer from current Z to ensure no part collisions.
|
||||
*
|
||||
* The shadow at each layer is computed by top-down unioning the part outline with
|
||||
* the shadow from the layer above.
|
||||
*
|
||||
* This operation is injected at the start of the operation chain before processing.
|
||||
*/
|
||||
class OpShadow extends CamOp {
|
||||
constructor(state, op) {
|
||||
super(state, op);
|
||||
|
|
@ -1726,7 +1762,9 @@ class OpShadow extends CamOp {
|
|||
|
||||
let tslices = [];
|
||||
let tshadow = [];
|
||||
let tzindex = slicer.interval(minStepDown, { fit: true, off: 0.01, down: true, flats: true });
|
||||
let tzindex = slicer.interval(minStepDown, {
|
||||
fit: true, off: 0.01, down: true, flats: true
|
||||
});
|
||||
let skipTerrain = unsafe;
|
||||
|
||||
if (skipTerrain) {
|
||||
|
|
@ -1737,10 +1775,14 @@ class OpShadow extends CamOp {
|
|||
let lsz; // only shadow up to bottom of last shadow for progressive union
|
||||
let cnt = 0;
|
||||
let tot = 0;
|
||||
|
||||
// terrain is the "shadow stack" where index 0 = top of part
|
||||
// thus array.length -1 = bottom of part
|
||||
let terrain = await slicer.slice(tzindex, { each: data => {
|
||||
let shadow = trueShadow ? shadowAt(data.z, lsz) : [];
|
||||
tshadow = POLY.union(tshadow.slice().appendAll(data.tops).appendAll(shadow), 0.01, true);
|
||||
tslices.push(data.slice);
|
||||
// capture current shadow for this slice
|
||||
data.slice.shadow = tshadow;
|
||||
if (false) {
|
||||
const slice = data.slice;
|
||||
|
|
@ -1775,14 +1817,23 @@ class OpShadow extends CamOp {
|
|||
throw `invalid widget shadow`;
|
||||
}
|
||||
|
||||
state.shadowTop = terrain[terrain.length - 1];
|
||||
// TODO: deprecate use of separate shadow vars in state
|
||||
state.center = tshadow[0].bounds.center();
|
||||
state.tshadow = tshadow; // true shadow (bottom of part)
|
||||
state.terrain = terrain; // stack of shadow slices with tops
|
||||
state.tslices = tslices;
|
||||
state.tshadow = tshadow; // true shadow (base of part)
|
||||
state.terrain = terrain; // stack of shadow slices stored in tops
|
||||
state.tslices = tslices; // raw slicer 'data' layer outputs
|
||||
state.skipTerrain = skipTerrain;
|
||||
|
||||
// identify through holes
|
||||
// TODO: refactor ops to use a unified shadow object
|
||||
state.shadow = {
|
||||
base: tshadow, // computed shadow union at base of part
|
||||
stack: terrain, // stack of shadow slices
|
||||
slices: tslices, // raw slicer 'data' objects
|
||||
skip: skipTerrain
|
||||
};
|
||||
|
||||
// identify through holes which are inner/child polygons
|
||||
// on the bottom-most layer of the shadow stack (tshadow, index == 0)
|
||||
state.thruHoles = tshadow.map(p => p.inner || []).flat();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
// dep: kiri.render
|
||||
// dep: kiri-mode.cam.driver
|
||||
// use: kiri-mode.cam.ops
|
||||
gapp.register("kiri-mode.cam.prepare", [], (root, exports) => {
|
||||
gapp.register("kiri-mode.cam.prepare", (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { paths, polygons, newPoint } = base;
|
||||
|
|
@ -562,7 +562,7 @@ function prepEach(widget, settings, print, firstPoint, update) {
|
|||
opSum += weight;
|
||||
if (tool && lastPoint) {
|
||||
newLayer();
|
||||
layerPush(printPoint = lastPoint.clone().setZ(zmax + zadd), 0, 0, tool);
|
||||
layerPush(printPoint = lastPoint.clone().setZ(zmax_outer), 0, 0, tool);
|
||||
newLayer();
|
||||
}
|
||||
}
|
||||
|
|
@ -606,7 +606,7 @@ function prepEach(widget, settings, print, firstPoint, update) {
|
|||
return start;
|
||||
}
|
||||
let ltops = tops[depth];
|
||||
let fitted = fit ? ltops.filter(poly => poly.isInside(fit, 0.01)) : ltops;
|
||||
let fitted = fit ? ltops.filter(poly => poly.isInside(fit, 0.05)) : ltops;
|
||||
let ftops = fitted.filter(top => !top.level_emit);
|
||||
if (ftops.length > 1) {
|
||||
ftops = POLY.route(ftops, start);
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@
|
|||
// use: kiri-mode.cam.slicer
|
||||
// use: kiri-mode.cam.slicer2
|
||||
// use: kiri-mode.cam.ops
|
||||
gapp.register("kiri-mode.cam.slice", [], (root, exports) => {
|
||||
gapp.register("kiri-mode.cam.slice", (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { util } = base;
|
||||
const { driver, newSlice, setSliceTracker } = kiri;
|
||||
const { polygons, newPoint, newPolygon } = base;
|
||||
const { CAM } = driver;
|
||||
|
|
@ -28,14 +29,14 @@ const POLY = polygons;
|
|||
* @param {Function} output
|
||||
*/
|
||||
CAM.slice = async function(settings, widget, onupdate, ondone) {
|
||||
let mesh = widget.mesh,
|
||||
proc = settings.process,
|
||||
let proc = settings.process,
|
||||
stock = settings.stock || {},
|
||||
isIndexed = proc.camStockIndexed,
|
||||
camOps = widget.camops = [],
|
||||
sliceAll = widget.slices = [],
|
||||
bounds = widget.getBoundingBox(),
|
||||
track = widget.track,
|
||||
{ camZTop, camZBottom, camZThru } = proc,
|
||||
// widget top z as defined by setTopz()
|
||||
wztop = track.top,
|
||||
// distance between top of part and top of stock
|
||||
|
|
@ -44,11 +45,11 @@ CAM.slice = async function(settings, widget, onupdate, ondone) {
|
|||
zbOff = isIndexed ? 0 : (wztop - track.box.d),
|
||||
// defined z bottom offset by distance to stock bottom
|
||||
// keeps the z bottom relative to the part when z align changes
|
||||
zBottom = isIndexed ? proc.camZBottom : proc.camZBottom - zbOff,
|
||||
zBottom = isIndexed ? camZBottom : camZBottom - zbOff,
|
||||
// greater of widget bottom and z bottom
|
||||
zMin = isIndexed ? bounds.min.z : Math.max(bounds.min.z, zBottom),
|
||||
zMax = bounds.max.z,
|
||||
zThru = proc.camZBottom ? 0 : (proc.camZThru || 0),
|
||||
zThru = camZThru,
|
||||
zTop = zMax + ztOff,
|
||||
minToolDiam = Infinity,
|
||||
maxToolDiam = -Infinity,
|
||||
|
|
@ -58,7 +59,37 @@ CAM.slice = async function(settings, widget, onupdate, ondone) {
|
|||
unsafe = proc.camExpertFast,
|
||||
units = settings.controller.units === 'in' ? 25.4 : 1,
|
||||
axisRotation,
|
||||
axisIndex;
|
||||
axisIndex,
|
||||
// new work area tracking
|
||||
part_size = bounds.dim,
|
||||
bottom_gap = zbOff,
|
||||
bottom_part = 0,
|
||||
bottom_stock = -bottom_gap,
|
||||
bottom_thru = zThru,
|
||||
bottom_z = Math.max(
|
||||
(camZBottom ? bottom_stock + camZBottom : bottom_part) - bottom_thru,
|
||||
(camZBottom ? bottom_stock + camZBottom : bottom_stock - bottom_thru)
|
||||
),
|
||||
bottom_cut = Math.max(bottom_z, -zThru),
|
||||
top_stock = zTop,
|
||||
top_part = zMax,
|
||||
top_gap = ztOff,
|
||||
top_z = camZTop ? bottom_stock + camZTop : top_stock,
|
||||
workarea = util.round({
|
||||
top_stock,
|
||||
top_part,
|
||||
top_gap,
|
||||
top_z,
|
||||
bottom_stock,
|
||||
bottom_part,
|
||||
bottom_gap,
|
||||
bottom_z,
|
||||
bottom_cut
|
||||
}, 3);
|
||||
|
||||
// console.table({ workarea });
|
||||
// console.table({ part_size });
|
||||
// console.table({ stock });
|
||||
|
||||
if (tabs) {
|
||||
// make tab polygons
|
||||
|
|
@ -66,7 +97,7 @@ CAM.slice = async function(settings, widget, onupdate, ondone) {
|
|||
let zero = newPoint(0,0,0);
|
||||
let point = newPoint(tab.pos.x, tab.pos.y, tab.pos.z);
|
||||
let poly = newPolygon().centerRectangle(zero, tab.dim.x, tab.dim.y);
|
||||
let tslice = newSlice(0);
|
||||
// let tslice = newSlice(0);
|
||||
let m4 = new THREE.Matrix4().makeRotationFromQuaternion(
|
||||
new THREE.Quaternion(tab.rot._x, tab.rot._y, tab.rot._z, tab.rot._w)
|
||||
);
|
||||
|
|
@ -203,7 +234,8 @@ CAM.slice = async function(settings, widget, onupdate, ondone) {
|
|||
zTop,
|
||||
unsafe,
|
||||
color,
|
||||
dark
|
||||
dark,
|
||||
// workarea
|
||||
};
|
||||
|
||||
let opList = [
|
||||
|
|
@ -236,9 +268,19 @@ CAM.slice = async function(settings, widget, onupdate, ondone) {
|
|||
|
||||
// call slice() function on all ops in order
|
||||
let tracker = setSliceTracker({ rotation: 0 });
|
||||
let workarea_orig = structuredClone(workarea);
|
||||
setAxisIndex();
|
||||
for (let op of opList) {
|
||||
let weight = op.weight();
|
||||
// apply operation override vars
|
||||
let workover = structuredClone(workarea_orig);
|
||||
let valz = op.op;
|
||||
if (valz.ov_topz) workover.top_z = bottom_stock + valz.ov_topz;
|
||||
if (valz.ov_botz) {
|
||||
workover.bottom_z = bottom_stock + valz.ov_botz;
|
||||
workover.bottom_cut = Math.max(workover.bottom_z, -zThru);
|
||||
}
|
||||
state.workarea = workover;
|
||||
await op.slice((progress, message) => {
|
||||
onupdate((opSum + (progress * weight)) / opTot, message || op.type());
|
||||
});
|
||||
|
|
@ -280,9 +322,9 @@ CAM.slice = async function(settings, widget, onupdate, ondone) {
|
|||
|
||||
// add shadow perimeter to terrain to catch outside moves off part
|
||||
let tabpoly = tabs ? tabs.map(tab => tab.poly) : [];
|
||||
let allpoly = POLY.union([...state.shadowTop.tops, ...tabpoly, ...state.shadowTop.slice.shadow], 0, true);
|
||||
let allpoly = POLY.union([...state.shadow.base, ...tabpoly], 0, true);
|
||||
let shadowOff = maxToolDiam < 0 ? allpoly :
|
||||
POLY.offset(allpoly, [minToolDiam/2,maxToolDiam/2], { count: 2, flat: true });
|
||||
POLY.offset(allpoly, [minToolDiam/2,maxToolDiam/2], { count: 2, flat: true, minArea: 0 });
|
||||
state.terrain.forEach(level => level.tops.appendAll(shadowOff));
|
||||
|
||||
widget.terrain = state.skipTerrain ? null : state.terrain;
|
||||
|
|
@ -366,7 +408,7 @@ CAM.traces = async function(settings, widget, single) {
|
|||
let trace = traces[i];
|
||||
let dz = Math.abs(z - trace.getZ());
|
||||
// only compare polys farther apart in Z
|
||||
if (dz < 0.01) {
|
||||
if (dz > 0.01) {
|
||||
continue;
|
||||
}
|
||||
// do not add duplicates
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ class Tool {
|
|||
return this.unitScale() * this.tool.taper_tip;
|
||||
}
|
||||
|
||||
maxDiameter() {
|
||||
return Math.max(this.fluteDiameter(), this.tipDiameter(), this.shaftDiameter());
|
||||
}
|
||||
|
||||
traceOffset() {
|
||||
return (this.isTaperMill() ? this.tipDiameter() : this.fluteDiameter()) / 2;
|
||||
}
|
||||
|
|
|
|||
302
src/kiri-mode/cam/tools.js
Normal file
302
src/kiri-mode/cam/tools.js
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/** 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) => {
|
||||
|
||||
let { kiri } = root,
|
||||
{ api, consts } = kiri,
|
||||
{ uc, ui } = api,
|
||||
{ MODES } = consts,
|
||||
DOC = document,
|
||||
selectedTool = null,
|
||||
editTools = null,
|
||||
maxTool = 0;
|
||||
|
||||
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 = ['endmill','ballmill','tapermill'].indexOf(tool.type);
|
||||
if (tool.type === 'tapermill') {
|
||||
ui.toolTaperAngle.value = kiri.driver.CAM.calcTaperAngle(
|
||||
(tool.flute_diam - tool.taper_tip) / 2, tool.flute_len
|
||||
).round(1);
|
||||
} 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';
|
||||
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');
|
||||
let pad = 10;
|
||||
let dim = { w: svg.clientWidth, h: svg.clientHeight }
|
||||
let max = { w: dim.w - pad * 2, h: dim.h - pad * 2};
|
||||
let off = { x: pad, y: pad };
|
||||
let shaft_fill = "#cccccc";
|
||||
let flute_fill = "#dddddd";
|
||||
let stroke = "#777777";
|
||||
let stroke_width = 3;
|
||||
let stroke_thin = stroke_width / 2;
|
||||
let shaft = tool.shaft_len || 1;
|
||||
let flute = tool.flute_len || 1;
|
||||
let tip_len = type === "ballmill" ? tool.flute_diam / 2 : 0;
|
||||
let total_len = shaft + flute + tip_len;
|
||||
let units = dim.h / total_len;
|
||||
let shaft_len = (shaft / total_len) * max.h;
|
||||
let flute_len = (flute / total_len) * max.h;
|
||||
let total_wid = Math.max(tool.flute_diam, tool.shaft_diam);
|
||||
let shaft_off = (max.w - tool.shaft_diam * units) / 2;
|
||||
let flute_off = (max.w - tool.flute_diam * units) / 2;
|
||||
let taper_off = (max.w - (tool.taper_tip || 0) * units) / 2;
|
||||
let parts = [
|
||||
{ rect: {
|
||||
x:off.x + shaft_off, y:off.y,
|
||||
width:max.w - shaft_off * 2, height:shaft_len,
|
||||
stroke, fill: shaft_fill, stroke_width
|
||||
} }
|
||||
];
|
||||
if (type === "tapermill") {
|
||||
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 {
|
||||
let x1 = off.x + flute_off;
|
||||
let y1 = off.y + shaft_len;
|
||||
let x2 = x1 + max.w - flute_off * 2;
|
||||
let y2 = y1 + flute_len;
|
||||
parts.push({ rect: {
|
||||
x:off.x + flute_off, y:off.y + shaft_len,
|
||||
width:max.w - flute_off * 2, height:flute_len,
|
||||
stroke, fill: flute_fill, stroke_width
|
||||
} });
|
||||
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 (type === "ballmill") {
|
||||
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;
|
||||
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 = ['endmill','ballmill','tapermill'][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.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();
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -73,6 +73,7 @@ class Topo4 {
|
|||
this.diam = tool.fluteDiameter();
|
||||
this.zoff = widget.track.top || 0;
|
||||
this.leave = op.leave || 0;
|
||||
this.linear = op.linear || false;
|
||||
this.lineColor = state.settings.controller.dark ? 0xffff00 : 0x555500;
|
||||
|
||||
onupdate(0, "lathe");
|
||||
|
|
@ -363,7 +364,7 @@ class Topo4 {
|
|||
}
|
||||
|
||||
async latheMinions(onupdate) {
|
||||
const { sliced, tool, zoff, leave, maxo, zBottom, step, resolution } = this;
|
||||
const { sliced, tool, zoff, leave, maxo, zBottom, step, resolution, linear } = this;
|
||||
const { putCache, clearCache, queue } = this;
|
||||
|
||||
const rota = this.angle * DEG2RAD;
|
||||
|
|
@ -404,19 +405,34 @@ class Topo4 {
|
|||
await Promise.all(promises);
|
||||
recs.sort((a, b) => { return b.angle - a.angle });
|
||||
|
||||
count = recs[0].heights.length / 3;
|
||||
// let linear = true;
|
||||
|
||||
count = linear ? recs.length : recs[0].heights.length / 3;
|
||||
while (count-- > 0) {
|
||||
let slice = newSlice(count);
|
||||
slice.camLines = [ newPolygon().setOpen() ];
|
||||
paths.push(slice);
|
||||
}
|
||||
|
||||
for (let rec of recs) {
|
||||
const { degrees, heights } = rec;
|
||||
[...heights].group(3).forEach((a,i) => {
|
||||
// progress each path 360 degrees to prevent A rolling backwards
|
||||
paths[i].camLines[0].push( newPoint(a[0], a[1], a[2] + leave).setA(degrees + i * -360) );
|
||||
if (linear) {
|
||||
recs.forEach((rec,i) => {
|
||||
const { degrees, heights } = rec;
|
||||
[...heights].group(3).forEach((a) => {
|
||||
// progress each path 360 degrees to prevent A rolling backwards
|
||||
paths[i].camLines[0].push( newPoint(a[0], a[1], a[2] + leave).setA(degrees + i * -360) );
|
||||
});
|
||||
if (i % 2 === 1) {
|
||||
paths[i].camLines[0].reverse();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let rec of recs) {
|
||||
const { degrees, heights } = rec;
|
||||
[...heights].group(3).forEach((a,i) => {
|
||||
// progress each path 360 degrees to prevent A rolling backwards
|
||||
paths[i].camLines[0].push( newPoint(a[0], a[1], a[2] + leave).setA(degrees + i * -360) );
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let slice of paths) {
|
||||
|
|
@ -425,9 +441,11 @@ class Topo4 {
|
|||
console.log('empty', slice);
|
||||
continue;
|
||||
}
|
||||
// repeat first point 360 degrees progressed
|
||||
const repeat = poly.points[0];
|
||||
slice.camLines[0].push(repeat.clone().setA(repeat.a - 360));
|
||||
if (!linear) {
|
||||
// repeat first point 360 degrees progressed
|
||||
const repeat = poly.points[0];
|
||||
slice.camLines[0].push(repeat.clone().setA(repeat.a - 360));
|
||||
}
|
||||
slice.output()
|
||||
.setLayer("lathe", { line: this.lineColor })
|
||||
.addPoly(poly.clone().applyRotations().move({ z: -zoff, x:0, y:0 }));
|
||||
|
|
|
|||
|
|
@ -14,14 +14,20 @@ const { getRangeParameters } = FDM;
|
|||
const debug = false;
|
||||
|
||||
FDM.export = function(print, online, ondone, ondebug) {
|
||||
const { settings, belty, tools } = print;
|
||||
const { widgets, settings, belty, tools, firstTool } = print;
|
||||
const { bounds, controller, device, process, filter, mode } = settings;
|
||||
const { extruders, fwRetract } = device;
|
||||
const { bedWidth, bedDepth, bedRound, bedBelt, maxHeight } = device;
|
||||
const { gcodeFan, gcodeLayer, gcodeTrack, gcodePause, gcodeFeature } = device;
|
||||
|
||||
let model_labels = [];
|
||||
for (let widget of widgets) {
|
||||
model_labels.push(widget.track.grid_id);
|
||||
}
|
||||
|
||||
let layers = print.output,
|
||||
extras = device.extras || {},
|
||||
isBambu = extras.bbl,
|
||||
{ extrudeAbs } = device,
|
||||
{ exportThumb } = controller,
|
||||
extused = Object.keys(print.extruders).map(v => parseInt(v)),
|
||||
|
|
@ -72,7 +78,7 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
y: isBelt ? 0 : bedDepth/2,
|
||||
z: 0
|
||||
},
|
||||
tool = 0,
|
||||
tool = firstTool || 0,
|
||||
extruder = extruders[tool],
|
||||
offset_x = extruder.extOffsetX,
|
||||
offset_y = extruder.extOffsetY,
|
||||
|
|
@ -107,8 +113,9 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
z_max: maxHeight,
|
||||
layers: layers.length,
|
||||
progress: 0,
|
||||
nozzle: 0,
|
||||
tool: 0
|
||||
nozzle: tool,
|
||||
tool: tool,
|
||||
model_labels: model_labels.sort().join(',')
|
||||
},
|
||||
pidx, path, out, speedMMM, emitMM, emitPerMM, lastp, laste, dist,
|
||||
lines = 0,
|
||||
|
|
@ -120,15 +127,6 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
minz = { x: Infinity, y: Infinity, z: Infinity },
|
||||
// lenghts of each filament (by nozzle) consumed
|
||||
segments = [],
|
||||
// palette & ping data
|
||||
isPalette = device.filamentSource === 'palette3',
|
||||
paletteInfo = extras.palette || {},
|
||||
palettePingStart = Math.max(paletteInfo.ping, paletteInfo.feed, paletteInfo.push) || 500,
|
||||
palettePingSpace = paletteInfo.ping || 0,
|
||||
// track purges for palette3 pings
|
||||
purgePos,
|
||||
purgeOn = 0,
|
||||
purgeOff = 0,
|
||||
extrudeMM = FDM.extrudeMM,
|
||||
extrudePerMM = FDM.extrudePerMM;
|
||||
|
||||
|
|
@ -138,6 +136,19 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
}
|
||||
subst.tool_count = tools_used.length;
|
||||
|
||||
// encodes an array offset as a single "on" bit in an
|
||||
// 8 byte array, then converts the array to base64 which
|
||||
// is what Bambu's M624 uses to flag an object as currently
|
||||
// being printed. the array is presented at the top of the
|
||||
// gcode as a comment: "; model label id: {model_labels}"
|
||||
function encodeBitOffset(index) {
|
||||
let bytes = new Uint8Array(8);
|
||||
let byteIndex = Math.floor(index / 8);
|
||||
let bitPosition = index % 8;
|
||||
bytes[byteIndex] |= (1 << bitPosition);
|
||||
return btoa(String.fromCharCode(...bytes));
|
||||
}
|
||||
|
||||
function setTempFanSpeed(tempSpeed) {
|
||||
if (tempSpeed > 0) {
|
||||
fanSpeedSave = fanSpeedSave >= 0 ? fanSpeedSave : fanSpeed;
|
||||
|
|
@ -239,7 +250,9 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
// with logic flow IF / ELIF / ELSE / END
|
||||
// IF / IF is valid but will not nest
|
||||
function appendSub(line, pad) {
|
||||
if (line.indexOf(';; IF ') === 0) {
|
||||
if (line.indexOf(';; DEFINE ') === 0) {
|
||||
// ignore var declarations
|
||||
} else if (line.indexOf(';; IF ') === 0) {
|
||||
line = line.substring(6).trim();
|
||||
let evil = print.constReplace(line, subst, 0, 666);
|
||||
subon = evil;
|
||||
|
|
@ -301,15 +314,16 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
append("; --- startup ---");
|
||||
}
|
||||
|
||||
// lookg for ";; PREAMBLE <MODE>" comment
|
||||
// looking for ";; PREAMBLE <MODE>" comment
|
||||
let pre = 0;
|
||||
let gcpre = [];
|
||||
for (let line of device.gcodePre) {
|
||||
line = line.trim();
|
||||
if (line.indexOf(";; PREAMBLE ") === 0) {
|
||||
if (line.indexOf(";; PREAMBLE") === 0) {
|
||||
if (line === ';; PREAMBLE OFF') pre = 1;
|
||||
if (line === ';; PREAMBLE END') pre = 2;
|
||||
} if (line.indexOf(";; AXISMAP ") === 0) {
|
||||
else if (line === ';; PREAMBLE END') pre = 2;
|
||||
else gcpre.push(pre = 123);
|
||||
} else if (line.indexOf(";; AXISMAP ") === 0) {
|
||||
let axmap = JSON.parse(line.substring(11).trim());
|
||||
for (let key in axmap) {
|
||||
axis[key] = ` ${axmap[key]}`;
|
||||
|
|
@ -324,6 +338,10 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
let t0 = false;
|
||||
let t1 = false;
|
||||
for (let line of gcpre) {
|
||||
if (line === pre) {
|
||||
preamble();
|
||||
continue;
|
||||
}
|
||||
if (line.indexOf('T0') === 0) t0 = true; else
|
||||
if (line.indexOf('T1') === 0) t1 = true; else
|
||||
if (line.indexOf('M82') === 0) {
|
||||
|
|
@ -360,15 +378,7 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
}
|
||||
});
|
||||
}
|
||||
if (line.indexOf("{tool}") > 0 && extused.length > 0) {
|
||||
for (let i of extused) {
|
||||
subst.tool = i;
|
||||
appendSubPad(line);
|
||||
}
|
||||
subst.tool = 0;
|
||||
} else {
|
||||
appendSubPad(line);
|
||||
}
|
||||
appendSubPad(line);
|
||||
}
|
||||
|
||||
if (pre === 2) preamble();
|
||||
|
|
@ -441,15 +451,6 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
}
|
||||
|
||||
function moveTo(newpos, rate, comment) {
|
||||
if (pingRemain) {
|
||||
if (newpos.e) {
|
||||
if (pingRemain - newpos.e < -0.4) {
|
||||
// split move if ping over-extrudes? complicates emitted calc.
|
||||
// console.log({over_ping: pingRemain - newpos.e});
|
||||
}
|
||||
pingRemain -= newpos.e;
|
||||
}
|
||||
}
|
||||
let o = [!rate && !newpos.e ? 'G0' : 'G1'];
|
||||
let emit = { x: false, y: false, z: false };
|
||||
if (typeof newpos.x === 'number' && newpos.x !== pos.x) {
|
||||
|
|
@ -527,9 +528,6 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
totaldistance += o1.point.distTo2D(o2.point);
|
||||
}, 1);
|
||||
|
||||
// for palette pings, amount of extrusion left
|
||||
let pingRemain = 0;
|
||||
|
||||
// retract before first move
|
||||
retract();
|
||||
|
||||
|
|
@ -610,44 +608,26 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
moveTo({z:zpos}, seekMMM);
|
||||
}
|
||||
|
||||
let cwidget;
|
||||
// iterate through layer outputs
|
||||
for (pidx=0; pidx<path.length; pidx++) {
|
||||
out = path[pidx];
|
||||
speedMMM = (out.speed || process.outputFeedrate) * 60; // range
|
||||
|
||||
// track purge towers for palette3
|
||||
// do not generate pings before total tube length exhausted
|
||||
// because the palette cannot respond to differences before then
|
||||
if (isPalette && palettePingSpace && emitted >= palettePingStart) {
|
||||
if (purgeOn === 0 && out.point.purgeOn && emitted - purgeOff >= palettePingSpace) {
|
||||
retract();
|
||||
pushPos(out.point.purgeOn);
|
||||
// shorter pause accounts for retract/move
|
||||
dwell(12750);
|
||||
popPos();
|
||||
unretract();
|
||||
purgeOn = emitted;
|
||||
purgePos = out.point.purgeOn;
|
||||
pingRemain = 20;
|
||||
// hint to controller that we're working on a specific object
|
||||
// so that gcode between start/stop comments can be cancelled
|
||||
if (out.widget !== cwidget) {
|
||||
if (cwidget) {
|
||||
append(`; end object id: ${cwidget.track.grid_id}`);
|
||||
isBambu && append('M625');
|
||||
}
|
||||
if (purgeOn && pingRemain <= 0) {
|
||||
retract();
|
||||
pushPos(purgePos);
|
||||
// shorter pause accounts for retract/move
|
||||
dwell(6750);
|
||||
popPos();
|
||||
unretract();
|
||||
if (!print.purges) {
|
||||
print.purges = [];
|
||||
}
|
||||
print.purges.push({
|
||||
length: purgeOn,
|
||||
extrusion: emitted - purgeOn
|
||||
});
|
||||
purgeOff = emitted;
|
||||
purgeOn = 0;
|
||||
pingRemain = 0;
|
||||
if (out.widget) {
|
||||
let off = model_labels.indexOf(out.widget.track.grid_id);
|
||||
let b64 = encodeBitOffset(off);
|
||||
append(`; start object id: ${out.widget.track.grid_id}`);
|
||||
isBambu && append(`M624 ${b64}`);
|
||||
}
|
||||
cwidget = out.widget;
|
||||
}
|
||||
|
||||
// emit comment on output type chage
|
||||
|
|
@ -662,8 +642,11 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
|
||||
// look for extruder change, run scripts, recalc emit factor
|
||||
if (out.tool !== undefined && out.tool != tool) {
|
||||
let macro_deselect = extruder.extDeselect.length ? extruder.extDeselect : extruders[0].extDeselect;
|
||||
let macro_select = extruder.extSelect.length ? extruder.extSelect : extruders[0].extSelect;
|
||||
segments.push({emitted, tool});
|
||||
appendAllSub(extruder.extDeselect);
|
||||
appendAllSub(macro_deselect);
|
||||
subst.last_tool = tool;
|
||||
tool = out.tool;
|
||||
subst.nozzle = subst.tool = tool;
|
||||
extruder = extruders[tool];
|
||||
|
|
@ -674,10 +657,7 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
extruder.extFilament,
|
||||
path.layer === 0 ?
|
||||
(process.firstSliceHeight || process.sliceHeight) : path.height);
|
||||
// do not run extruder swapping code when source is Palette3
|
||||
if (!isPalette) {
|
||||
appendAllSub(extruder.extSelect);
|
||||
}
|
||||
appendAllSub(macro_select);
|
||||
}
|
||||
|
||||
// if no point in output, it's a dwell command
|
||||
|
|
@ -848,6 +828,11 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
laste = out.emit;
|
||||
}
|
||||
layer++;
|
||||
if (cwidget) {
|
||||
append(`; end object id: ${cwidget.track.grid_id}`);
|
||||
isBambu && append('M625');
|
||||
cwidget = undefined;
|
||||
}
|
||||
|
||||
// end open loop when detected
|
||||
while (endloop-- > 0) {
|
||||
|
|
@ -1002,11 +987,11 @@ FDM.export = function(print, online, ondone, ondebug) {
|
|||
// force emit of buffer
|
||||
append();
|
||||
// console.log({segments, emitted, outputLength});
|
||||
print.segments = isPalette ? segments : undefined;
|
||||
print.distance = emitted;
|
||||
print.lines = lines;
|
||||
print.bytes = bytes + lines - 1;
|
||||
print.time = time;
|
||||
print.labels = model_labels;
|
||||
|
||||
if (debug) {
|
||||
console.log('segments', segments);
|
||||
|
|
|
|||
|
|
@ -111,36 +111,42 @@ function fillGyroid(target) {
|
|||
let tile_z = 1 / tile;
|
||||
let gyroid = base.gyroid.slice(target.zValue() * tile_z, (1 - density) * 500);
|
||||
|
||||
// gyroid.polys.forEach(poly => {
|
||||
// for (let tx=0; tx<=tile_x; tx++) {
|
||||
// for (let ty=0; ty<=tile_y; ty++) {
|
||||
// target.newline();
|
||||
// let bx = tx * tile + bounds.min.x;
|
||||
// let by = ty * tile + bounds.min.y;
|
||||
// poly.forEach(point => {
|
||||
// target.emit(bx + point.x * tile, by + point.y * tile);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
let polys = [];
|
||||
for (let tx=0; tx<=tile_x; tx++) {
|
||||
if (gyroid.dir == 'lr') {
|
||||
for (let ty=0; ty<=tile_y; ty++) {
|
||||
for (let poly of gyroid.polys) {
|
||||
target.newline();
|
||||
let points = poly.map(el => {
|
||||
return {
|
||||
x: el.x * tile + tx * tile + bounds.min.x,
|
||||
y: el.y * tile + ty * tile + bounds.min.y,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
polys.push(base.newPolygon().setOpen(true).addObj(points));
|
||||
for (let tx=0; tx<=tile_x; tx++) {
|
||||
for (let poly of gyroid.polys) {
|
||||
target.newline();
|
||||
let points = poly.map(el => {
|
||||
return {
|
||||
x: el.x * tile + tx * tile + bounds.min.x,
|
||||
y: el.y * tile + ty * tile + bounds.min.y,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
polys.push(base.newPolygon().setOpen(true).addObj(points));
|
||||
}
|
||||
}
|
||||
polys = connectOpenPolys(polys);
|
||||
}
|
||||
} else {
|
||||
for (let tx=0; tx<=tile_x; tx++) {
|
||||
for (let ty=0; ty<=tile_y; ty++) {
|
||||
for (let poly of gyroid.polys) {
|
||||
target.newline();
|
||||
let points = poly.map(el => {
|
||||
return {
|
||||
x: el.x * tile + tx * tile + bounds.min.x,
|
||||
y: el.y * tile + ty * tile + bounds.min.y,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
polys.push(base.newPolygon().setOpen(true).addObj(points));
|
||||
}
|
||||
}
|
||||
polys = connectOpenPolys(polys);
|
||||
}
|
||||
}
|
||||
polys = connectOpenPolys(polys);
|
||||
for (let poly of polys.filter(p => p.perimeter() > 2)) {
|
||||
target.newline();
|
||||
for (let point of poly.points) {
|
||||
|
|
@ -180,7 +186,8 @@ function connectOpenPolys(noff, dist = 0.1) {
|
|||
continue outer;
|
||||
}
|
||||
if (s1.last().distTo2D(s2.last()) <= dist) {
|
||||
s1.addPoints(s2.points.reverse());
|
||||
s2.reverse();
|
||||
s1.addPoints(s2.points);
|
||||
ntmp[j] = null;
|
||||
continue outer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ FDM.prepare = async function(widgets, settings, update) {
|
|||
return a.z - b.z;
|
||||
});
|
||||
|
||||
let firstExt;
|
||||
let firstTool;
|
||||
let lastWidget;
|
||||
let lastExt;
|
||||
let lastOut;
|
||||
|
|
@ -516,8 +516,8 @@ FDM.prepare = async function(widgets, settings, update) {
|
|||
return a.dst - b.dst;
|
||||
});
|
||||
let { z, slice, offset } = order[0];
|
||||
if (firstExt === undefined) {
|
||||
firstExt = slice.extruder;
|
||||
if (firstTool === undefined) {
|
||||
firstTool = slice.extruder;
|
||||
}
|
||||
|
||||
// when layers switch between widgets, force retraction
|
||||
|
|
@ -549,6 +549,7 @@ FDM.prepare = async function(widgets, settings, update) {
|
|||
let beltStart = slice.belt && slice.belt.touch;// && (widgets.length === 1);
|
||||
// output seek to start point between mesh slices if previous data
|
||||
print.setType('layer');
|
||||
print.setWidget(lastWidget);
|
||||
printPoint = slicePrintPath(
|
||||
print,
|
||||
slice,
|
||||
|
|
@ -579,6 +580,7 @@ FDM.prepare = async function(widgets, settings, update) {
|
|||
}
|
||||
}
|
||||
);
|
||||
print.setWidget(null);
|
||||
|
||||
lastOut = slice;
|
||||
lastExt = lastOut.extruder;
|
||||
|
|
@ -644,6 +646,7 @@ FDM.prepare = async function(widgets, settings, update) {
|
|||
}
|
||||
|
||||
print.output = output;
|
||||
print.firstTool = firstTool;
|
||||
|
||||
// post-process for base extrusions (touching the bed)
|
||||
if (isBelt) {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,19 @@ FDM.sliceAll = function(settings, onupdate) {
|
|||
.sort((a,b) => {
|
||||
return a.slices[0].z - b.slices[0].z
|
||||
});
|
||||
// assign grid_id which can be embedded in gcode and
|
||||
// used by the controller to cancel objects during print
|
||||
let { bounds } = settings;
|
||||
for (let widget of widgets) {
|
||||
let { pos, box } = widget.track;
|
||||
// calculate top/left coordinate for widget
|
||||
// relative to bounding box for all widgets
|
||||
let tl = {
|
||||
x: Math.round((pos.x - box.w/2 - bounds.min.x) / 10) + 1,
|
||||
y: Math.round((pos.y - box.h/2 - bounds.min.y) / 10) + 1
|
||||
};
|
||||
widget.track.grid_id = tl.x * 100 + tl.y;
|
||||
}
|
||||
// count extruders used
|
||||
let ext = [];
|
||||
for (let w of widgets) {
|
||||
|
|
@ -104,6 +117,7 @@ FDM.slice = function(settings, widget, onupdate, ondone) {
|
|||
let render = settings.render !== false,
|
||||
{ process, device, controller } = settings,
|
||||
isBelt = device.bedBelt,
|
||||
isBrick = controller.devel && process.sliceZInterleave,
|
||||
isSynth = widget.track.synth,
|
||||
isSupport = widget.track.support,
|
||||
useAssembly = controller.assembly,
|
||||
|
|
@ -857,6 +871,52 @@ FDM.slice = function(settings, widget, onupdate, ondone) {
|
|||
profileEnd();
|
||||
}
|
||||
|
||||
if (isBrick) {
|
||||
let indices = slices.map(s => s.index);
|
||||
let first = indices[1];
|
||||
let last = indices[indices.length - 2];
|
||||
let nu = [];
|
||||
for (let slice of slices) {
|
||||
if (slice.index < first || slice.index > last) {
|
||||
continue;
|
||||
}
|
||||
let nuSlice = slice.clone();
|
||||
nuSlice.z -= slice.height / 2;
|
||||
if (slice.index === first) {
|
||||
nuSlice.z = slice.z - slice.height / 4;
|
||||
nuSlice.height = slice.height / 2;
|
||||
} else {
|
||||
nuSlice.height = slice.height;
|
||||
}
|
||||
nu.push(nuSlice);
|
||||
let ti = 0;
|
||||
for (let top of slice.tops) {
|
||||
let nuTop = nuSlice.tops[ti++];
|
||||
nuTop.shells = [];
|
||||
top.shells = top.shells.filter((s,i) => {
|
||||
if (i % 2 === 0) {
|
||||
return true;
|
||||
} else {
|
||||
nuTop.shells.push(s);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (slice.index === last) {
|
||||
let cap = nuSlice.clone();
|
||||
cap.z += (slice.height * 0.75);
|
||||
cap.height = (slice.height / 2);
|
||||
nu.push(cap);
|
||||
cap.tops.forEach((top, i) => {
|
||||
top.shells = nuSlice.tops[i].shells.clone();
|
||||
});
|
||||
}
|
||||
}
|
||||
slices.appendAll(nu);
|
||||
slices.sort((a,b) => a.z - b.z);
|
||||
slices.forEach((s,i) => s.index = i);
|
||||
}
|
||||
|
||||
// render if not explicitly disabled
|
||||
if (render) {
|
||||
forSlices(0.9, 1.0, slice => {
|
||||
|
|
@ -1054,6 +1114,7 @@ function doShells(slice, count, offset1, offsetN, fillOffset, opt = {}) {
|
|||
}
|
||||
|
||||
slice.tops.forEach(function(top) {
|
||||
if (!top.fill_off) return; // missing for inner brick layers
|
||||
let lines = fillArea(top.fill_off, angle, spacing, null);
|
||||
top.fill_lines.appendAll(lines);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,14 +45,15 @@ function polyLabel(poly, label) {
|
|||
}
|
||||
|
||||
function sliceEmitObjects(print, slice, groups, opt = { }) {
|
||||
let process = print.settings.process;
|
||||
let stacked = process.ctOutStack;
|
||||
let grouped = stacked || process.ctOutGroup;
|
||||
let label = false && process.outputLaserLabel;
|
||||
let simple = opt.simple || false;
|
||||
let emit = { in: [], out: [], mark: [] };
|
||||
let lastEmit = opt.lastEmit;
|
||||
let zcolor = print.settings.process.ctOutZColor;
|
||||
let process = print.settings.process,
|
||||
marked = process.ctOutMark,
|
||||
stacked = process.ctOutStack,
|
||||
grouped = marked || process.ctOutGroup,
|
||||
label = false && process.outputLaserLabel,
|
||||
simple = opt.simple || false,
|
||||
emit = { in: [], out: [], mark: [] },
|
||||
lastEmit = opt.lastEmit,
|
||||
zcolor = print.settings.process.ctOutZColor;
|
||||
|
||||
function polyOut(poly, group, type, indexed) {
|
||||
if (!poly) {
|
||||
|
|
@ -77,7 +78,7 @@ function sliceEmitObjects(print, slice, groups, opt = { }) {
|
|||
print.PPP(poly, group, pathOpt);
|
||||
// when stacking (top down widget slices), if the last layer is fully contained
|
||||
// by the current layer, then it is "marked" onto the current layer in a different color
|
||||
if (stacked && type === "out" && lastEmit) {
|
||||
if (marked && type === "out" && lastEmit) {
|
||||
for (let out of lastEmit.out) {
|
||||
if (out.isInside(poly)) {
|
||||
polyOut(out, group, "mark");
|
||||
|
|
@ -297,9 +298,12 @@ async function prepare(widgets, settings, update) {
|
|||
// emit objects from each slice into output array
|
||||
let layers = [];
|
||||
for (let widget of widgets) {
|
||||
if (process.ctOutMerged) {
|
||||
// slice stack merging
|
||||
// there is not layout in this mode
|
||||
if (process.ctOutStack) {
|
||||
// 3d stack output, no merging or layout
|
||||
for (let slice of widget.slices) {
|
||||
}
|
||||
} else if (process.ctOutMerged) {
|
||||
// slice stack merging, no layout
|
||||
// there are no inner vs outer polys
|
||||
// merged/same polys "increase" color/weight
|
||||
let merged = [];
|
||||
|
|
@ -557,7 +561,8 @@ function exportGCode(settings, data) {
|
|||
let dev = settings.device;
|
||||
let proc = settings.process;
|
||||
let space = dev.gcodeSpace ? ' ' : '';
|
||||
let power = 255;
|
||||
let max_power = dev.laserMaxPower || 255;
|
||||
let power = max_power;
|
||||
let cut_on = dev.gcodeLaserOn || dev.gcodeWaterOn || dev.gcodeKnifeDn || [];
|
||||
let cut_off = dev.gcodeLaserOff || dev.gcodeWaterOff || dev.gcodeKnifeUp || [];
|
||||
let knifeOn = proc.knifeOn;
|
||||
|
|
@ -567,14 +572,14 @@ function exportGCode(settings, data) {
|
|||
exportElements(
|
||||
settings,
|
||||
data,
|
||||
function(min, max, power, speed) {
|
||||
function(min, max, pct, speed) {
|
||||
let width = (max.x - min.x),
|
||||
height = (max.y - min.y);
|
||||
|
||||
dx = min.x;
|
||||
dy = min.y;
|
||||
feedrate = `${space}F${speed}`;
|
||||
power = (256 * (power / 100)).toFixed(3);
|
||||
power = (max_power * (pct / 100)).toFixed(3);
|
||||
|
||||
(dev.gcodePre || []).forEach(line => {
|
||||
lines.push(line);
|
||||
|
|
@ -640,7 +645,7 @@ function exportGCode(settings, data) {
|
|||
function exportSVG(settings, data, cut_color) {
|
||||
let { process } = settings;
|
||||
let zcolor = process.ctOutZColor ? 1 : 0;
|
||||
let zstack = process.ctOutStack;
|
||||
let zstack = process.ctOutMark;
|
||||
let lines = [], dx = 0, dy = 0, my, z = 0;
|
||||
let colors = [
|
||||
"black",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ function init(kiri, api) {
|
|||
print_min = print_min.toString().padStart(2,'0');
|
||||
print_hrs = print_hrs.toString().padStart(2,'0');
|
||||
|
||||
$('print-filename').value = filename;
|
||||
$('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}`;
|
||||
|
|
@ -94,7 +94,7 @@ function init(kiri, api) {
|
|||
}
|
||||
|
||||
function saveFile(api, file, ext) {
|
||||
api.util.download(file, $('print-filename').value + ext);
|
||||
api.util.download(file, $('print-filename-sla').value + ext);
|
||||
api.modal.hide();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -494,15 +494,29 @@ kiri.worker = {
|
|||
send.data({debug});
|
||||
});
|
||||
const {
|
||||
bounds, time, lines, bytes, distance,
|
||||
settings, segments, purges
|
||||
bounds,
|
||||
time,
|
||||
lines,
|
||||
bytes,
|
||||
distance,
|
||||
settings,
|
||||
segments,
|
||||
purges,
|
||||
labels
|
||||
} = current.print;
|
||||
|
||||
send.done({
|
||||
done: true,
|
||||
output: output ? output : {
|
||||
bounds, time, lines, bytes, distance,
|
||||
settings, segments, purges
|
||||
bounds,
|
||||
time,
|
||||
lines,
|
||||
bytes,
|
||||
distance,
|
||||
settings,
|
||||
segments,
|
||||
purges,
|
||||
labels
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -67,10 +67,11 @@ function update(clear) {
|
|||
}
|
||||
}
|
||||
|
||||
api.alerts = {
|
||||
// extend API
|
||||
Object.assign(api.alerts, {
|
||||
hide,
|
||||
show,
|
||||
update
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
213
src/kiri/api.js
213
src/kiri/api.js
|
|
@ -6,59 +6,134 @@
|
|||
// dep: data.local
|
||||
// dep: kiri.utils
|
||||
// dep: kiri.consts
|
||||
gapp.register("kiri.api", [], (root, exports) => {
|
||||
gapp.register("kiri.api", (root, exports) => {
|
||||
|
||||
const { data, kiri, noop } = root;
|
||||
const { consts, utils } = kiri;
|
||||
const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o } = utils;
|
||||
const { LISTS } = consts;
|
||||
|
||||
let isHover = false;
|
||||
|
||||
const 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_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
|
||||
modes: [ "fdm", "sla", "cam", "laser" ], // enable device modes
|
||||
pmode: consts.PMODES.SPEED, // preview modes
|
||||
// hover: false, // when true fires mouse hover events
|
||||
get hover() {
|
||||
return isHover;
|
||||
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);
|
||||
}
|
||||
},
|
||||
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
|
||||
});
|
||||
|
||||
const devel = {
|
||||
xray: (layers, raw) => {
|
||||
let proc = api.conf.get().process;
|
||||
let size = proc.sliceHeight || proc.slaSlice || 1;
|
||||
let 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();
|
||||
}
|
||||
};
|
||||
|
||||
const 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;
|
||||
|
|
@ -71,42 +146,4 @@ function localSet(key, val) {
|
|||
return val;
|
||||
}
|
||||
|
||||
const api = exports({
|
||||
clip: (text) => {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.catch(err => console.error('Clipboard Error:', err));
|
||||
},
|
||||
clone: Object.clone,
|
||||
sdb: data.local,
|
||||
ajax: ajax,
|
||||
js2o: js2o,
|
||||
o2js: o2js,
|
||||
lists: LISTS,
|
||||
doit: {
|
||||
undo: noop, // set in do.js
|
||||
redo: noop // set in do.js
|
||||
},
|
||||
var: {
|
||||
layer_lo: 0,
|
||||
layer_hi: 0,
|
||||
layer_max: 0
|
||||
},
|
||||
feature,
|
||||
devel,
|
||||
tweak,
|
||||
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),
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
|
||||
// dep: add.array
|
||||
// dep: data.local
|
||||
gapp.register("kiri.conf", [], (root, exports) => {
|
||||
gapp.register("kiri.conf", (root, exports) => {
|
||||
|
||||
const { data } = root;
|
||||
const { local } = data;
|
||||
const { clone } = Object;
|
||||
const CVER = 185;
|
||||
const CVER = 410;
|
||||
|
||||
function genID() {
|
||||
while (true) {
|
||||
|
|
@ -79,21 +79,6 @@ function forValues(o, fn) {
|
|||
Object.values(o).forEach(v => fn(v));
|
||||
}
|
||||
|
||||
function device_v1_to_v2(device) {
|
||||
if (device && device.filamentSize) {
|
||||
device.extruders = [{
|
||||
extFilament: device.filamentSize,
|
||||
extNozzle: device.nozzleSize,
|
||||
extSelect: ["T0"],
|
||||
extDeselect: [],
|
||||
extOffsetX: 0,
|
||||
extOffsetY: 0
|
||||
}];
|
||||
delete device.filamentSize;
|
||||
delete device.nozzleSize;
|
||||
}
|
||||
}
|
||||
|
||||
// convert default filter (from server) into device structure
|
||||
function device_from_code(code,mode) {
|
||||
// presence of internal field indicates already converted
|
||||
|
|
@ -176,18 +161,6 @@ function normalize(settings) {
|
|||
default_dev = defaults[mode].d,
|
||||
default_pro = defaults[mode].p;
|
||||
|
||||
// v1 to v2 changed FDM extruder / nozzle / filament structure
|
||||
if (settings.ver != CVER) {
|
||||
// backup settings before upgrade
|
||||
local.setItem(`ws-settings-${Date.now()}`, JSON.stringify(settings));
|
||||
device_v1_to_v2(settings.device);
|
||||
device_v1_to_v2(settings.cdev.FDM);
|
||||
objectMap(settings.devices, dev => {
|
||||
return dev ? device_from_code(dev) : dev;
|
||||
});
|
||||
settings.ver = CVER;
|
||||
}
|
||||
|
||||
// fixup old/new detail settings
|
||||
let detail = settings.controller.detail;
|
||||
settings.controller.detail = {
|
||||
|
|
@ -292,7 +265,7 @@ const conf = exports({
|
|||
extOffsetY: 0
|
||||
}],
|
||||
profiles: [],
|
||||
// other stored info like palette3 config
|
||||
// other stored config info
|
||||
extras: {}
|
||||
},
|
||||
// process defaults FDM:Process
|
||||
|
|
@ -327,6 +300,7 @@ const conf = exports({
|
|||
sliceSupportNozzle: 0,
|
||||
sliceSupportEnable: false,
|
||||
sliceSupportOutline: true,
|
||||
sliceZInterleave: false,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceBottomLayers: 3,
|
||||
sliceTopLayers: 3,
|
||||
|
|
@ -522,8 +496,11 @@ const conf = exports({
|
|||
camTraceSpeed: 250,
|
||||
camTracePlunge: 200,
|
||||
camTraceOffOver: 0,
|
||||
camTraceDogbone: false,
|
||||
camTraceMerge: true,
|
||||
camTraceLines: false,
|
||||
camTraceBottom: false,
|
||||
camTraceZTop: 0,
|
||||
camTraceZBottom: 0,
|
||||
camPocketSpindle: 1000,
|
||||
camPocketTool: 1000,
|
||||
camPocketOver: 0.25,
|
||||
|
|
@ -537,6 +514,8 @@ const conf = exports({
|
|||
camPocketContour: false,
|
||||
camPocketEngrave: false,
|
||||
camPocketOutline: false,
|
||||
camPocketZTop: 0,
|
||||
camPocketZBottom: 0,
|
||||
camDrillTool: 1000,
|
||||
camDrillSpindle: 1000,
|
||||
camDrillDownSpeed: 250,
|
||||
|
|
@ -573,6 +552,7 @@ const conf = exports({
|
|||
camOriginTop: true,
|
||||
camZAnchor: "middle",
|
||||
camZOffset: 0,
|
||||
camZTop: 0,
|
||||
camZBottom: 0,
|
||||
camZClearance: 1,
|
||||
camZThru: 0,
|
||||
|
|
@ -619,6 +599,7 @@ const conf = exports({
|
|||
bedDepth: 200,
|
||||
bedHeight: 2.5,
|
||||
maxHeight: 100,
|
||||
laserMaxPower: 255,
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeFExt: "",
|
||||
|
|
@ -639,6 +620,7 @@ const conf = exports({
|
|||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutMark: false,
|
||||
ctOutStack: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: true,
|
||||
|
|
@ -682,7 +664,7 @@ const conf = exports({
|
|||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutStack: false,
|
||||
ctOutMark: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: true,
|
||||
ctOriginBounds: false,
|
||||
|
|
@ -690,7 +672,8 @@ const conf = exports({
|
|||
outputInvertY: false,
|
||||
ctOutKnifeDepth: 1,
|
||||
ctOutKnifePasses: 1,
|
||||
ctOutKnifeTip: 2
|
||||
ctOutKnifeTip: 2,
|
||||
ctOutStack: true,
|
||||
},
|
||||
},
|
||||
wjet: {
|
||||
|
|
@ -725,7 +708,7 @@ const conf = exports({
|
|||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutStack: false,
|
||||
ctOutMark: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: true,
|
||||
ctOriginBounds: false,
|
||||
|
|
@ -733,7 +716,8 @@ const conf = exports({
|
|||
outputInvertY: false,
|
||||
ctOutKnifeDepth: 1,
|
||||
ctOutKnifePasses: 1,
|
||||
ctOutKnifeTip: 2
|
||||
ctOutKnifeTip: 2,
|
||||
ctOutStack: false,
|
||||
},
|
||||
},
|
||||
wedm: {
|
||||
|
|
@ -767,14 +751,15 @@ const conf = exports({
|
|||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutStack: false,
|
||||
ctOutMark: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: false,
|
||||
ctOriginBounds: true,
|
||||
ctOriginOffX: 0,
|
||||
ctOriginOffY: 0,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false
|
||||
outputInvertY: false,
|
||||
ctOutStack: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -851,11 +836,11 @@ const conf = exports({
|
|||
taper_tip: 0,
|
||||
}
|
||||
],
|
||||
// currently selected device
|
||||
// currently selected device (current mode)
|
||||
device:{},
|
||||
// currently selected process
|
||||
// currently selected process (current mode)
|
||||
process:{},
|
||||
// current process name by mode
|
||||
// current process (name of last used) by mode
|
||||
cproc:{
|
||||
FDM: "default",
|
||||
SLA: "default",
|
||||
|
|
@ -865,7 +850,7 @@ const conf = exports({
|
|||
WEDM: "default",
|
||||
LASER: "default",
|
||||
},
|
||||
// stored processes by mode
|
||||
// stored process (copy of last used) by mode
|
||||
sproc:{
|
||||
FDM: {},
|
||||
SLA: {},
|
||||
|
|
@ -875,7 +860,7 @@ const conf = exports({
|
|||
WEDM: {},
|
||||
LASER: {},
|
||||
},
|
||||
// current device name by mode
|
||||
// current device (name of last used) by mode
|
||||
filter:{
|
||||
FDM: "Any.Generic.Marlin",
|
||||
SLA: "Anycubic.Photon",
|
||||
|
|
@ -885,7 +870,7 @@ const conf = exports({
|
|||
WEDM: "RackRobo.Betta.Wire.V1",
|
||||
LASER: "Any.Generic.Laser",
|
||||
},
|
||||
// stored device by mode
|
||||
// current (last used) device by mode
|
||||
cdev: {
|
||||
FDM: null,
|
||||
SLA: null,
|
||||
|
|
@ -895,9 +880,7 @@ const conf = exports({
|
|||
},
|
||||
// custom devices by name (all modes)
|
||||
devices:{},
|
||||
// favorited devices (all modes)
|
||||
favorites:{},
|
||||
// map of device to last process setting (name)
|
||||
// map of device name to last process setting name
|
||||
devproc: {},
|
||||
// application ui and control preferences (Q menu)
|
||||
controller:{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.consts", [], (root, exports) => {
|
||||
gapp.register("kiri.consts", (root, exports) => {
|
||||
|
||||
const COLOR = {
|
||||
wireframe: 0x444444,
|
||||
|
|
@ -62,10 +62,6 @@ const LISTS = {
|
|||
{ name: "flat" },
|
||||
{ name: "line" }
|
||||
],
|
||||
filasrc: [
|
||||
{ name: "direct" },
|
||||
{ name: "palette3" }
|
||||
],
|
||||
animesh: [
|
||||
{ name: "100" },
|
||||
{ name: "200" },
|
||||
|
|
|
|||
395
src/kiri/devices.js
Normal file
395
src/kiri/devices.js
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.settings
|
||||
gapp.register("kiri.devices", (root, exports) => {
|
||||
|
||||
let { kiri } = root,
|
||||
{ api, conf } = kiri;
|
||||
|
||||
// extend API
|
||||
Object.assign(api.show, {
|
||||
devices: showDevices
|
||||
});
|
||||
|
||||
Object.assign(api.device, {
|
||||
clone: cloneDevice,
|
||||
code: currentDeviceCode,
|
||||
get: currentDeviceName,
|
||||
set: selectDevice,
|
||||
isBelt
|
||||
});
|
||||
|
||||
Object.assign(api.devices, {
|
||||
show: showDevices,
|
||||
select: selectDevice,
|
||||
refresh: updateDeviceList,
|
||||
update_laser_state: updateLaserState
|
||||
});
|
||||
|
||||
function isBelt() {
|
||||
return api.conf.get().device.bedBelt;
|
||||
}
|
||||
|
||||
function currentDeviceName() {
|
||||
return api.conf.get().filter[api.mode.get()];
|
||||
}
|
||||
|
||||
function currentDeviceCode() {
|
||||
return api.conf.get().devices[currentDeviceName()];
|
||||
}
|
||||
|
||||
function getModeDevices() {
|
||||
// devices are injected into self scope by
|
||||
// app.js generateDevices()
|
||||
return Object.keys(devices[api.mode.get_lower()]).sort();
|
||||
}
|
||||
|
||||
function showDevices() {
|
||||
api.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 = api.conf.get().devices;
|
||||
if (newname !== selected) {
|
||||
devs[newname] = devs[selected];
|
||||
delete devs[selected];
|
||||
selectDevice(newname);
|
||||
updateDeviceList();
|
||||
}
|
||||
}
|
||||
|
||||
function putLocalDevice(devicename, obj) {
|
||||
api.conf.get().devices[devicename] = obj;
|
||||
api.conf.save();
|
||||
}
|
||||
|
||||
function removeLocalDevice(devicename) {
|
||||
delete api.conf.get().devices[devicename];
|
||||
api.conf.save();
|
||||
api.settings.sync.put();
|
||||
}
|
||||
|
||||
function isLocalDevice(devicename) {
|
||||
return api.conf.get().devices[devicename] ? true : false;
|
||||
}
|
||||
|
||||
function getSelectedDevice() {
|
||||
return api.device.get();
|
||||
}
|
||||
|
||||
function selectDevice(devicename) {
|
||||
if (isLocalDevice(devicename)) {
|
||||
setDeviceCode(api.conf.get().devices[devicename], devicename);
|
||||
} else {
|
||||
let code = devices[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(api.conf.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);
|
||||
api.settings.sync.put();
|
||||
}
|
||||
|
||||
function updateLaserState() {
|
||||
const dev = api.conf.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 = api.conf.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;
|
||||
let { space } = kiri;
|
||||
|
||||
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,
|
||||
ui.extSelect,
|
||||
ui.extDeselect
|
||||
].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;
|
||||
|
||||
api.conf.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
|
||||
api.conf.load(null, dproc);
|
||||
} else {
|
||||
api.conf.update();
|
||||
}
|
||||
|
||||
api.conf.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 = api.conf.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();
|
||||
api.conf.save();
|
||||
api.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);
|
||||
api.conf.save();
|
||||
api.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 ? api.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);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
// dep: ext.md5
|
||||
// dep: geo.base
|
||||
// dep: data.local
|
||||
// dep: kiri.consts
|
||||
|
|
@ -109,36 +110,36 @@ function exportLaserDialog(data, names) {
|
|||
function download_svg() {
|
||||
api.util.download(
|
||||
driver.exportSVG(settings, data),
|
||||
$('print-filename').value + ".svg"
|
||||
$('print-filename-laser').value + ".svg"
|
||||
);
|
||||
}
|
||||
|
||||
function download_dxf() {
|
||||
api.util.download(
|
||||
driver.exportDXF(settings, data),
|
||||
$('print-filename').value + ".dxf"
|
||||
$('print-filename-laser').value + ".dxf"
|
||||
);
|
||||
}
|
||||
|
||||
function download_gcode() {
|
||||
api.util.download(
|
||||
driver.exportGCode(settings, data),
|
||||
$('print-filename').value + ".gcode"
|
||||
$('print-filename-laser').value + ".gcode"
|
||||
);
|
||||
}
|
||||
|
||||
// api.ajax("/kiri/output-laser.html", function(html) {
|
||||
api.modal.show('xlaser');
|
||||
let segments = 0;
|
||||
data.forEach(layer => { segments += layer.length });
|
||||
// ui.print.innerHTML = html;
|
||||
$('print-filename').value = filename;
|
||||
$('print-lines').value = util.comma(segments);
|
||||
$('print-svg').onclick = download_svg;
|
||||
$('print-dxf').onclick = download_dxf;
|
||||
$('print-lg').onclick = download_gcode;
|
||||
// api.modal.show('print');
|
||||
// });
|
||||
api.modal.show('xlaser');
|
||||
|
||||
let segments = 0;
|
||||
data.forEach(layer => { segments += layer.length });
|
||||
|
||||
$('print-filename-laser').value = filename;
|
||||
$('print-lines').value = util.comma(segments);
|
||||
$('print-svg').onclick = download_svg;
|
||||
$('print-dxf').onclick = download_dxf;
|
||||
$('print-lg').onclick = download_gcode;
|
||||
|
||||
console.log('laser export', { fileroot, filename });
|
||||
}
|
||||
|
||||
function bindField(field, varname) {
|
||||
|
|
@ -441,14 +442,15 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
function calcWeight() {
|
||||
try {
|
||||
let density = $('print-density');
|
||||
$('print-weight').value = (
|
||||
info.weight = (
|
||||
(Math.PI * util.sqr(
|
||||
info.settings.device.extruders[0].extFilament / 2
|
||||
)) *
|
||||
info.distance *
|
||||
(parseFloat(density.value) || 1.25) /
|
||||
1000
|
||||
).toFixed(2);
|
||||
).round(2);
|
||||
$('print-weight').value = info.weight.toFixed(2);
|
||||
density.onkeyup = (ev) => {
|
||||
if (ev.key === 'Enter') calcWeight();
|
||||
};
|
||||
|
|
@ -467,8 +469,6 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
}
|
||||
|
||||
api.modal.show('xany');
|
||||
// fetch("/kiri/output-gcode.html").then(r => r.text()).then(html => {
|
||||
// ui.print.innerHTML = html;
|
||||
let set = api.conf.get();
|
||||
let fdm = MODE === MODES.FDM;
|
||||
let octo = set.controller.exportOcto && MODE !== MODES.CAM;
|
||||
|
|
@ -478,8 +478,7 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
$('code-preview-head').style.display = preview ? '' : 'none';
|
||||
$('code-preview').style.display = preview ? '' : 'none';
|
||||
$('print-download').onclick = download;
|
||||
$('print-filament-head').style.display = fdm ? '' : 'none';
|
||||
$('print-filament-info').style.display = fdm ? '' : 'none';
|
||||
$('print-filament').style.display = fdm ? '' : 'none';
|
||||
$('print-filename').value = filename;
|
||||
$('print-filesize').value = util.comma(info.bytes);
|
||||
$('print-filament').value = Math.round(info.distance);
|
||||
|
|
@ -517,133 +516,176 @@ function exportGCodeDialog(gcode, sections, info, names) {
|
|||
})
|
||||
};
|
||||
|
||||
// in palette mode, show download button
|
||||
let downloadPalette = $('print-palette');
|
||||
downloadPalette.style.display = info.segments ? 'flex' : 'none';
|
||||
// generate MAFX downloadble file
|
||||
if (info.segments) {
|
||||
// todo: reduce segments to eliminate 0 lenghts and transitions before 150mm
|
||||
let { settings, segments } = info;
|
||||
let { device, bounds } = settings;
|
||||
let { min, max } = bounds;
|
||||
let extras = device.extras || {};
|
||||
let pinfo = extras.palette || {};
|
||||
// filter pings to those occuring after all tubes combined
|
||||
let pings = info.purges || [];
|
||||
let driveInfo = {};
|
||||
let volume = {};
|
||||
let length = {};
|
||||
// clean up and round pings
|
||||
pings.forEach(p => {
|
||||
p.length = p.length.round(3);
|
||||
// p.length = (p.length - pinfo.offset).round(2);
|
||||
// p.length = (p.length + pinfo.offset).round(2);
|
||||
p.extrusion = p.extrusion.round(3);
|
||||
});
|
||||
// add length of push filament to the last segment
|
||||
segments.peek().emitted += pinfo.push;
|
||||
let lastEmit = 0;
|
||||
let ratioVolume = (0.4 * 0.4) / (1.75 * 1.75);
|
||||
for (let seg of segments) {
|
||||
let seginfo = driveInfo[seg.tool] = driveInfo[seg.tool] || { length: 0, volume: 0 };
|
||||
seg.emitted += pinfo.offset;
|
||||
seginfo.length += seg.emitted - lastEmit;
|
||||
seginfo.volume = seginfo.length * ratioVolume;
|
||||
volume[seg.tool+1] = seginfo.volume.round(2);
|
||||
length[seg.tool+1] = seginfo.length.round(2);
|
||||
lastEmit = seg.emitted;
|
||||
}
|
||||
let totalLength = Object.values(length).reduce((a,v) => a+v).round(2);
|
||||
let totalVolume = Object.values(volume).reduce((a,v) => a+v).round(2);
|
||||
// console.log({info, device, pinfo, segments, volume, length, totalLength});
|
||||
let meta = {
|
||||
version: "3.2",
|
||||
printerProfile: {
|
||||
id: pinfo.printer,
|
||||
name: device.deviceName || "My Printer"
|
||||
},
|
||||
preheatTemperature: { nozzle: [0], bed: 0 },
|
||||
paletteNozzle: 0,
|
||||
time: info.time.round(1),
|
||||
volume,
|
||||
length,
|
||||
totalLength,
|
||||
totalVolume,
|
||||
inputsUsed: Object.keys(driveInfo).length,
|
||||
splices: segments.length,
|
||||
pings: pings.length,
|
||||
boundingBox: {
|
||||
min: [ min.x, min.y, min.z ],
|
||||
max: [ max.x, max.y, max.z ]
|
||||
},
|
||||
filaments: Object.keys(driveInfo).map(v => { return {
|
||||
name: `Color${v}`,
|
||||
type: "Filament",
|
||||
color: `#${v}0${v}0${v}0`,
|
||||
materialId: parseInt(v) + 1,
|
||||
filamentId: parseInt(v) + 1
|
||||
}}),
|
||||
};
|
||||
let lastDrive;
|
||||
let algokeys = {};
|
||||
let algorithms = [];
|
||||
let defaultSplice = {
|
||||
compression: pinfo.press,
|
||||
cooling: pinfo.cool,
|
||||
heat: pinfo.heat
|
||||
};
|
||||
for (let key of Object.keys(driveInfo)) {
|
||||
key = parseInt(key) + 1;
|
||||
algorithms.push({
|
||||
ingoingId: key,
|
||||
outgoingId: key,
|
||||
...defaultSplice
|
||||
});
|
||||
}
|
||||
let palette = {
|
||||
version: "3.0",
|
||||
drives: [0, 0, 0, 0, 0, 0, 0, 0].map((v,i) => {
|
||||
return driveInfo[i] ? i+1 : 0
|
||||
}),
|
||||
splices: segments.filter(r => {
|
||||
return r.emitted >= 150;
|
||||
}).map(r => {
|
||||
if (lastDrive >= 0 && lastDrive !== r.tool) {
|
||||
let key = `${lastDrive}+${r.tool}`;
|
||||
if (!algokeys[key]) {
|
||||
let rec = algokeys[key] = {
|
||||
ingoingId: lastDrive + 1,
|
||||
outgoingId: r.tool + 1,
|
||||
...defaultSplice
|
||||
};
|
||||
algorithms.push(rec);
|
||||
}
|
||||
}
|
||||
lastDrive = r.tool;
|
||||
return { id: r.tool + 1, length: r.emitted.round(2) }
|
||||
}),
|
||||
pings,
|
||||
algorithms
|
||||
};
|
||||
let png;
|
||||
kiri.client.png({}, data => {
|
||||
png = data.png;
|
||||
});
|
||||
console.log({meta,palette});
|
||||
downloadPalette.onclick = function() {
|
||||
kiri.client.zip([
|
||||
{name:"meta.json", data:JSON.stringify(meta,undefined,4)},
|
||||
{name:"palette.json", data:JSON.stringify(palette,undefined,4)},
|
||||
{name:"thumbnail.png", data:png.buffer}
|
||||
], progress => {
|
||||
api.show.progress(progress.percent/100, "generating palette files");
|
||||
}, output => {
|
||||
api.show.progress(0);
|
||||
api.util.download(output, `${$('print-filename').value}.mafx`);
|
||||
})
|
||||
};
|
||||
// in fdm mode, show 3mf file option
|
||||
let nozzle0 = set.device?.extruders?.[0]?.extNozzle || 0.4;
|
||||
let download3MF = $('print-3mf');
|
||||
download3MF.style.display = fdm ? 'flex' : 'none';
|
||||
download3MF.onclick = function() {
|
||||
gen3mf(zip => api.util.download(zip, `${$('print-filename').value}.3mf`));
|
||||
};
|
||||
|
||||
// present bambu print options when selected device is bambu
|
||||
if (api.bambu) {
|
||||
api.bambu.prep_export(gen3mf, gcode, info, settings);
|
||||
}
|
||||
|
||||
// let wids = api.widgets.all();
|
||||
// let bnds = settings.bounds;
|
||||
// console.log({ wids, bnds });
|
||||
|
||||
function gen3mf(then, ptype = 'unknown', ams = [0]) {
|
||||
let now = new Date();
|
||||
let ymd = [
|
||||
now.getFullYear(),
|
||||
(now.getMonth() + 1).toString().padStart(2,0),
|
||||
now.getDate().toString().padStart(2,0),
|
||||
].join('-');
|
||||
let files = [{
|
||||
name: `[Content_Types].xml`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
|
||||
' <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
|
||||
' <Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>',
|
||||
' <Default Extension="gcode" ContentType="application/octet-stream"/>',
|
||||
'</Types>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `_rels/.rels`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
|
||||
' <Relationship Target="/3D/3dmodel.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>',
|
||||
'</Relationships>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `3D/3dmodel.model`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:BambuStudio="http://schemas.bambulab.com/package/2021">',
|
||||
' <metadata name="Application">Kiri:Moto</metadata>',
|
||||
' <metadata name="Copyright"></metadata>',
|
||||
` <metadata name="CreationDate">${ymd}</metadata>`,
|
||||
' <metadata name="Description"></metadata>',
|
||||
' <metadata name="Designer"></metadata>',
|
||||
' <metadata name="DesignerCover"></metadata>',
|
||||
' <metadata name="License"></metadata>',
|
||||
` <metadata name="ModificationDate">${ymd}</metadata>`,
|
||||
' <metadata name="Origin"></metadata>',
|
||||
' <metadata name="Title"></metadata>',
|
||||
' <resources>',
|
||||
' </resources>',
|
||||
' <build/>',
|
||||
'</model>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `Metadata/_rels/model_settings.config.rels`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
|
||||
' <Relationship Target="/Metadata/plate_1.gcode" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/gcode"/>',
|
||||
'</Relationships>'
|
||||
].join('\n')
|
||||
// },{
|
||||
// name: `Metadata/plate_1.json`,
|
||||
// data: JSON.stringify({
|
||||
// "bbox_all": [ 100, 100, 200, 200 ],
|
||||
// "bbox_objects": (info.labels || []).map(label => {
|
||||
// return {
|
||||
// area: 600,
|
||||
// bbox: [ 100, 100, 200, 200 ],
|
||||
// id: label,
|
||||
// layer_height: 0.2,
|
||||
// name: "Object"
|
||||
// }
|
||||
// }),
|
||||
// "bed_type": "textured_plate",
|
||||
// "filament_colors": ["#FFFFFF"],
|
||||
// "filament_ids": ams,
|
||||
// "first_extruder": ams[0],
|
||||
// "is_seq_print": false,
|
||||
// "nozzle_diameter": 0.6,
|
||||
// "version": 2
|
||||
// })
|
||||
},{
|
||||
name: `Metadata/model_settings.config`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<config>',
|
||||
' <plate>',
|
||||
' <metadata key="plater_id" value="1"/>',
|
||||
' <metadata key="plater_name" value=""/>',
|
||||
' <metadata key="locked" value="false"/>',
|
||||
' <metadata key="gcode_file" value="Metadata/plate_1.gcode"/>',
|
||||
' <metadata key="thumbnail_file" value="Metadata/plate_1.png"/>',
|
||||
' <metadata key="thumbnail_no_light_file" value="Metadata/plate_no_light_1.png"/>',
|
||||
' <metadata key="top_file" value="Metadata/top_1.png"/>',
|
||||
' <metadata key="pick_file" value="Metadata/pick_1.png"/>',
|
||||
' </plate>',
|
||||
'</config>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `Metadata/slice_info.config`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<config>',
|
||||
' <header>',
|
||||
' <header_item key="X-BBL-Client-Type" value="slicer"/>',
|
||||
' <header_item key="X-BBL-Client-Version" value="01.10.01.50"/>',
|
||||
' </header>',
|
||||
' <plate>',
|
||||
' <metadata key="index" value="1"/>',
|
||||
// setting this value allows for the printer to error out if
|
||||
// the gcode / 3mf was intended for a different target type.
|
||||
// leaving it blank bypasses the check
|
||||
// ` <metadata key="printer_model_id" value="${ptype}"/>`,
|
||||
` <metadata key="nozzle_diameters" value="${nozzle0}"/>`,
|
||||
' <metadata key="timelapse_type" value="0"/>',
|
||||
` <metadata key="prediction" value="${Math.round(info.time)}"/>`,
|
||||
` <metadata key="weight" value="${info.weight}"/>`,
|
||||
' <metadata key="outside" value="false"/>',
|
||||
' <metadata key="support_used" value="false"/>',
|
||||
' <metadata key="label_object_enabled" value="false"/>',
|
||||
// (info.labels || []).map(label =>
|
||||
// ` <object identify_id="${label}" name="Object" skipped="false" />`),
|
||||
// ' <filament id="1" tray_info_idx="GFL96" type="PLA" color="#FFFFFF" used_m="0.17" used_g="0.50" />',
|
||||
// ' <warning msg="bed_temperature_too_high_than_filament" level="1" error_code ="1000C001" />',
|
||||
' </plate>',
|
||||
'</config>'
|
||||
].filter(v => v).flat().join('\n')
|
||||
},{
|
||||
name: `Metadata/project_settings.config`,
|
||||
data: JSON.stringify({},undefined,4)
|
||||
},{
|
||||
name: `Metadata/plate_1.gcode`,
|
||||
data: gcode
|
||||
},{
|
||||
name: `Metadata/plate_1.gcode.md5`,
|
||||
data: ext.md5.hash(gcode)
|
||||
},{
|
||||
name: `Metadata/plate_1_small.png`,
|
||||
data: api.view.bambu.s128.png
|
||||
},{
|
||||
name: `Metadata/plate_no_light_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
},{
|
||||
name: `Metadata/plate_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
},{
|
||||
name: `Metadata/pick_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
},{
|
||||
name: `Metadata/top_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
}];
|
||||
kiri.client.zip(files, progress => {
|
||||
api.show.progress(progress.percent/100, "generating 3mf");
|
||||
}, output => {
|
||||
api.show.progress(0);
|
||||
then(output);
|
||||
})
|
||||
};
|
||||
|
||||
// octoprint setup
|
||||
$('send-to-octohead').style.display = octo ? '' : 'none';
|
||||
$('send-to-octoprint').style.display = octo ? '' : 'none';
|
||||
|
|
@ -696,10 +738,6 @@ 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);
|
||||
|
||||
// show dialog
|
||||
// api.modal.show('print');
|
||||
// });
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
// dep: kiri-mode.drag.driver
|
||||
// dep: kiri-mode.wjet.driver
|
||||
// dep: kiri-mode.wedm.driver
|
||||
gapp.register("kiri.function", [], (root, exports) => {
|
||||
gapp.register("kiri.function", (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { api, client, consts, utils } = kiri;
|
||||
|
|
@ -31,7 +31,10 @@ function prepareSlices(callback, scale = 1, offset = 0) {
|
|||
// this can be used later by exports and rendered on some devices
|
||||
let snap = space.screenshot();
|
||||
view.snapshot = snap.substring(snap.indexOf(",") + 1);
|
||||
client.snap(space.screenshot2({width: 640}));
|
||||
client.snap(space.screenshot2({ width: 640 }));
|
||||
let bambu = view.bambu = { };
|
||||
space.screenshot3({ width: 512, out(png) { bambu.s512 = png } });
|
||||
space.screenshot3({ width: 128, out(png) { bambu.s128 = png } });
|
||||
}
|
||||
|
||||
if (mode.is_sla() && !callback) {
|
||||
|
|
@ -439,7 +442,7 @@ function parseCode(code, type) {
|
|||
}
|
||||
|
||||
// extend API (api.function)
|
||||
const functions = api.function = {
|
||||
const functions = Object.assign(api.function, {
|
||||
slice: prepareSlices,
|
||||
print: preparePreview,
|
||||
prepare: preparePreview,
|
||||
|
|
@ -449,6 +452,6 @@ const functions = api.function = {
|
|||
parse: parseCode,
|
||||
clear: client.clear,
|
||||
clear_progress() { complete = {} }
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
975
src/kiri/init.js
975
src/kiri/init.js
File diff suppressed because it is too large
Load diff
175
src/kiri/main.js
175
src/kiri/main.js
|
|
@ -15,6 +15,7 @@
|
|||
// dep: kiri.widget
|
||||
// dep: kiri.stats
|
||||
// dep: kiri.stacks
|
||||
// dep: kiri.devices
|
||||
// dep: kiri.function
|
||||
// dep: kiri.platform
|
||||
// dep: kiri.selection
|
||||
|
|
@ -23,19 +24,17 @@
|
|||
// use: kiri.files
|
||||
// use: kiri.frame
|
||||
// use: moto.ajax
|
||||
gapp.register("kiri.main", [], (root, exports) => {
|
||||
gapp.register("kiri.main", (root, exports) => {
|
||||
|
||||
const { base, data, kiri, moto, noop } = root;
|
||||
const { api, consts, lang, Widget, newWidget, utils, stats } = kiri;
|
||||
const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o, ls2o } = utils;
|
||||
const { feature, platform, selection, settings } = api;
|
||||
const { COLOR, MODES, PMODES, VIEWS } = consts;
|
||||
|
||||
const LANG = lang.current,
|
||||
let { data, kiri, moto, noop } = root,
|
||||
{ api, consts, lang, Widget, newWidget, utils, stats } = kiri,
|
||||
{ parseOpt, encodeOpt, o2js, js2o, ls2o } = utils,
|
||||
{ platform, selection, settings } = api,
|
||||
{ COLOR, MODES, VIEWS } = consts,
|
||||
LANG = lang.current,
|
||||
WIN = self.window,
|
||||
DOC = self.document,
|
||||
LOC = self.location,
|
||||
HOST = LOC.host.split(':'),
|
||||
SETUP = parseOpt(LOC.search.substring(1)),
|
||||
SECURE = isSecure(LOC.protocol),
|
||||
LOCAL = self.debug && !SETUP.remote,
|
||||
|
|
@ -43,93 +42,73 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
SDB = data.local,
|
||||
SPACE = kiri.space = moto.space,
|
||||
FILES = kiri.catalog = kiri.openFiles(new data.Index(SETUP.d ? SETUP.d[0] : 'kiri')),
|
||||
CONF = kiri.conf,
|
||||
clone = Object.clone;
|
||||
|
||||
let UI = {},
|
||||
clone = Object.clone,
|
||||
UI = {},
|
||||
UC = kiri.ui.prefix('kiri').inputAction(api.conf.update),
|
||||
MODE = MODES.FDM,
|
||||
STACKS = kiri.stacks,
|
||||
DRIVER = undefined,
|
||||
viewMode = VIEWS.ARRANGE,
|
||||
local = SETUP.local,
|
||||
autoSaveTimer = null,
|
||||
busy = 0,
|
||||
showFavorites = SDB.getItem('dev-favorites') === 'true',
|
||||
saveTimer = null,
|
||||
version = kiri.version = gapp.version;
|
||||
{ assign } = Object;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// augment api
|
||||
Object.assign(api, {
|
||||
ui: UI,
|
||||
uc: UC,
|
||||
// extend API
|
||||
assign(api, {
|
||||
ui: UI = assign(api.ui, UI),
|
||||
uc: UC = assign(api.uc, UC),
|
||||
stats,
|
||||
focus: noop,
|
||||
catalog: FILES,
|
||||
busy: {
|
||||
busy: assign(api.busy, {
|
||||
val() { return busy },
|
||||
inc() { kiri.api.event.emit("busy", ++busy) },
|
||||
dec() { kiri.api.event.emit("busy", --busy) }
|
||||
},
|
||||
}),
|
||||
color: COLOR,
|
||||
const: {
|
||||
const: assign(api.const, {
|
||||
LANG,
|
||||
LOCAL,
|
||||
SETUP,
|
||||
SECURE,
|
||||
STACKS,
|
||||
},
|
||||
device: {
|
||||
code: currentDeviceCode,
|
||||
get: currentDeviceName,
|
||||
set: noop, // set during init
|
||||
clone: noop // set during init
|
||||
},
|
||||
dialog: {
|
||||
}),
|
||||
dialog: assign(api.dialog, {
|
||||
show: showModal,
|
||||
hide: hideModal,
|
||||
update_process_list: updateProcessList
|
||||
},
|
||||
help: {
|
||||
}),
|
||||
help: assign(api.help, {
|
||||
show: showHelp,
|
||||
file: showHelpFile
|
||||
},
|
||||
event: {
|
||||
}),
|
||||
event: assign(api.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
|
||||
},
|
||||
group: {
|
||||
}),
|
||||
group: assign(api.group, {
|
||||
merge: groupMerge,
|
||||
split: groupSplit,
|
||||
},
|
||||
hide: {
|
||||
}),
|
||||
hide: assign(api.hide, {
|
||||
alert(rec, recs) { api.alerts.hide(...arguments) },
|
||||
import: noop,
|
||||
slider: hideSlider
|
||||
},
|
||||
image: {
|
||||
}),
|
||||
image: assign(api.image, {
|
||||
dialog: loadImageDialog,
|
||||
convert: loadImageConvert
|
||||
},
|
||||
}),
|
||||
language: kiri.lang,
|
||||
modal: {
|
||||
modal: assign(api.modal, {
|
||||
show: showModal,
|
||||
hide: hideModal,
|
||||
visible: modalShowing
|
||||
},
|
||||
mode: {
|
||||
}),
|
||||
mode: assign(api.mode, {
|
||||
get_id() { return MODE },
|
||||
get_lower: getModeLower,
|
||||
get: getMode,
|
||||
|
|
@ -149,29 +128,26 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
api.mode.is_wjet() ||
|
||||
api.mode.is_laser()
|
||||
}
|
||||
},
|
||||
probe: {
|
||||
}),
|
||||
probe: assign(api.probe, {
|
||||
live: "https://live.grid.space",
|
||||
grid: noop,
|
||||
local: noop
|
||||
},
|
||||
process: {
|
||||
}),
|
||||
process: assign(api.process, {
|
||||
code: currentProcessCode,
|
||||
get: currentProcessName
|
||||
},
|
||||
show: {
|
||||
}),
|
||||
show: assign(api.show, {
|
||||
alert() { return api.alerts.show(...arguments) },
|
||||
devices: noop, // set during init
|
||||
progress: setProgress,
|
||||
controls: setControlsVisible,
|
||||
favorites: getShowFavorites,
|
||||
slices: showSlices,
|
||||
layer: setVisibleLayer,
|
||||
local: showLocal,
|
||||
tools: noop, // set during init
|
||||
import: function() { UI.import.style.display = '' }
|
||||
},
|
||||
space: {
|
||||
}),
|
||||
space: assign(api.space, {
|
||||
reload,
|
||||
auto_save,
|
||||
restore: restoreWorkspace,
|
||||
|
|
@ -180,16 +156,16 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
set_focus: setFocus,
|
||||
update: SPACE.update,
|
||||
is_dark() { return settings.ctrl().dark }
|
||||
},
|
||||
util: {
|
||||
}),
|
||||
util: assign(api.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))) }
|
||||
},
|
||||
view: {
|
||||
}),
|
||||
view: assign(api.view, {
|
||||
get() { return viewMode },
|
||||
set() { setViewMode(...arguments) },
|
||||
set_arrange() { api.view.set(VIEWS.ARRANGE) },
|
||||
|
|
@ -209,10 +185,19 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
edges: setEdges,
|
||||
unit_scale: unitScale,
|
||||
wireframe: setWireframe,
|
||||
},
|
||||
}),
|
||||
work: kiri.client
|
||||
});
|
||||
|
||||
// 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();
|
||||
const { stacks } = kiri;
|
||||
|
|
@ -274,8 +259,8 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
if (!settings.ctrl().autoSave) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(() => {
|
||||
clearTimeout(autoSaveTimer);
|
||||
autoSaveTimer = setTimeout(() => {
|
||||
api.space.save(true);
|
||||
}, 1000);
|
||||
}
|
||||
|
|
@ -284,7 +269,7 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
let inits = parseInt(SDB.getItem('kiri-init') || stats.get('init') || 0) + 1;
|
||||
SDB.setItem('kiri-init', inits);
|
||||
stats.set('init', inits);
|
||||
stats.set('kiri', kiri.version);
|
||||
stats.set('kiri', kiri.version || gapp.version);
|
||||
|
||||
// remove version from url, preserve other settings
|
||||
WIN.history.replaceState({},'','/kiri/' + encodeOpt(SETUP) + LOC.hash);
|
||||
|
|
@ -297,15 +282,6 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
return api.mode.is_cam() && settings.ctrl().units === 'in' ? 25.4 : 1;
|
||||
}
|
||||
|
||||
function getShowFavorites(bool) {
|
||||
if (bool !== undefined) {
|
||||
SDB.setItem('dev-favorites', bool);
|
||||
showFavorites = bool;
|
||||
return bool;
|
||||
}
|
||||
return showFavorites;
|
||||
}
|
||||
|
||||
function triggerSettingsEvent() {
|
||||
api.event.emit('settings', settings.get());
|
||||
}
|
||||
|
|
@ -450,11 +426,6 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
api.var.layer_hi = layer;
|
||||
api.event.emit("slider.label");
|
||||
|
||||
let cam = api.mode.is_cam(),
|
||||
sla = api.mode.is_sla(),
|
||||
hi = cam ? api.var.layer_max - api.var.layer_lo : api.var.layer_hi,
|
||||
lo = cam ? api.var.layer_max - api.var.layer_hi : api.var.layer_lo;
|
||||
|
||||
updateSlider();
|
||||
STACKS.setRange(api.var.layer_lo, api.var.layer_hi);
|
||||
|
||||
|
|
@ -513,7 +484,7 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
}
|
||||
|
||||
function loadImage(image, opt = {}) {
|
||||
const info = Object.assign({settings: settings.get(), png:image}, opt);
|
||||
const info = assign({settings: settings.get(), png:image}, opt);
|
||||
kiri.client.image2mesh(info, progress => {
|
||||
api.show.progress(progress, "converting");
|
||||
}, vertices => {
|
||||
|
|
@ -670,8 +641,9 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
visible = modalShowing(),
|
||||
info = { pct: 0 };
|
||||
|
||||
["help","setup","tools","prefs","saves","files","xany","xlaser","xsla","local","any"].forEach(name => {
|
||||
UI[name].style.display = name === which ? 'flex' : '';
|
||||
// hide all modals befroe showing another
|
||||
Object.keys(UI.modals).forEach(name => {
|
||||
UI.modals[name].style.display = name === which ? 'flex' : '';
|
||||
});
|
||||
|
||||
function ondone() {
|
||||
|
|
@ -702,7 +674,10 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
easing(TWEEN.Easing.Quadratic.InOut).
|
||||
to({pct:0}, 100).
|
||||
onUpdate(() => { style.height = `${info.pct}%` }).
|
||||
onComplete(() => { style.display = '' }).
|
||||
onComplete(() => {
|
||||
style.display = '';
|
||||
api.event.emit('modal.hide');
|
||||
}).
|
||||
start();
|
||||
}
|
||||
|
||||
|
|
@ -868,7 +843,6 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
const oldMode = viewMode;
|
||||
const isCAM = settings.mode() === 'CAM';
|
||||
viewMode = mode;
|
||||
platform.deselect();
|
||||
|
|
@ -955,7 +929,7 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
// restore cached device profile for this mode
|
||||
if (current.cdev[mode]) {
|
||||
current.device = clone(current.cdev[mode]);
|
||||
api.event.emit('device.select', currentDeviceName());
|
||||
api.event.emit('device.select', api.device.get());
|
||||
}
|
||||
// hide/show
|
||||
api.uc.setVisible($('set-tools'), mode === 'CAM');
|
||||
|
|
@ -981,14 +955,6 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
}
|
||||
}
|
||||
|
||||
function currentDeviceName() {
|
||||
return settings.get().filter[getMode()];
|
||||
}
|
||||
|
||||
function currentDeviceCode() {
|
||||
return settings.get().devices[currentDeviceName()];
|
||||
}
|
||||
|
||||
function currentProcessName() {
|
||||
return settings.get().cproc[getMode()];
|
||||
}
|
||||
|
|
@ -1012,9 +978,6 @@ gapp.register("kiri.main", [], (root, exports) => {
|
|||
// prevent safari from exiting full screen mode
|
||||
DOC.onkeydown = function (evt) { if (evt.keyCode == 27) evt.preventDefault() }
|
||||
|
||||
// complete module loading
|
||||
// kiri.load_exec();
|
||||
|
||||
// upon restore, seed presets
|
||||
api.event.emit('preset', api.conf.dbo());
|
||||
|
||||
|
|
|
|||
|
|
@ -921,7 +921,7 @@ function fitDeviceToWidgets() {
|
|||
}
|
||||
|
||||
// extend API (api.platform)
|
||||
const platform = api.platform = {
|
||||
const platform = Object.assign(api.platform, {
|
||||
fit: fitDeviceToWidgets,
|
||||
add: platformAdd,
|
||||
changed: platformChanged,
|
||||
|
|
@ -950,6 +950,6 @@ const platform = api.platform = {
|
|||
show_volume: space.platform.showVolume,
|
||||
top_z() { return topZ },
|
||||
clear() { api.space.clear(); api.space.save(true) }
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ class Print {
|
|||
this.nextType = type;
|
||||
}
|
||||
|
||||
// allows for gcode object id annotations enabling
|
||||
// discrete object cancellation during print (bambu)
|
||||
setWidget(widget) {
|
||||
this.widget = widget;
|
||||
}
|
||||
|
||||
addOutput(array, point, emit, speed, tool, type) {
|
||||
let { lastPoint, lastEmit, lastOut } = this;
|
||||
// drop duplicates (usually intruced by FDM bisections)
|
||||
|
|
@ -52,6 +58,7 @@ class Print {
|
|||
if (tool !== undefined) {
|
||||
this.tools[tool] = true;
|
||||
}
|
||||
lastOut.widget = this.widget;
|
||||
array.push(lastOut);
|
||||
this.nextType = undefined;
|
||||
return lastOut;
|
||||
|
|
@ -582,7 +589,7 @@ class Print {
|
|||
scope.belt = belt;
|
||||
|
||||
if (scope.debugE) {
|
||||
console.log({ print_time: time.round(2) });
|
||||
console.log({ bounds, print_time: time.round(2) });
|
||||
}
|
||||
|
||||
done({ output: scope.output });
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
// dep: kiri.api
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.utils
|
||||
gapp.register("kiri.selection", [], (root, exports) => {
|
||||
gapp.register("kiri.selection", (root, exports) => {
|
||||
|
||||
const { kiri, moto, noop } = self;
|
||||
const { api, consts, utils } = kiri;
|
||||
|
|
@ -296,7 +296,7 @@ function setDisabled(bool) {
|
|||
}
|
||||
|
||||
// extend API (api.selection)
|
||||
const selection = api.selection = {
|
||||
const selection = Object.assign(api.selection, {
|
||||
move,
|
||||
merge,
|
||||
scale,
|
||||
|
|
@ -320,6 +320,6 @@ const selection = api.selection = {
|
|||
disable() { setDisabled(true) },
|
||||
opacity() { api.widgets.opacity(...arguments) },
|
||||
meshes() { return selectedMeshes.slice() },
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
// dep: data.local
|
||||
// use: kiri.widgets
|
||||
// use: ext.base64
|
||||
gapp.register("kiri.settings", [], (root, exports) => {
|
||||
gapp.register("kiri.settings", (root, exports) => {
|
||||
|
||||
const { data, kiri, moto, noop } = self;
|
||||
const { api, conf, consts, utils } = kiri;
|
||||
|
|
@ -762,7 +762,7 @@ function setEnableWASM(bool) {
|
|||
}
|
||||
|
||||
// extend API (api.conf)
|
||||
api.conf = {
|
||||
Object.assign(api.conf, {
|
||||
dbo: () => { return ls2o('ws-settings') },
|
||||
get: getSettings,
|
||||
put: putSettings,
|
||||
|
|
@ -778,10 +778,10 @@ api.conf = {
|
|||
restore: restoreSettings,
|
||||
export: settingsExport,
|
||||
import: settingsImport,
|
||||
};
|
||||
});
|
||||
|
||||
// extend API (api.settings)
|
||||
api.settings = {
|
||||
Object.assign(api.settings, {
|
||||
get: getSettings,
|
||||
import: settingsImport,
|
||||
import_zip: settingsImportZip,
|
||||
|
|
@ -797,6 +797,6 @@ api.settings = {
|
|||
async put() {},
|
||||
status: false
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// dep: add.three
|
||||
// dep: kiri.api
|
||||
gapp.register("kiri.tools", [], (root, exports) => {
|
||||
gapp.register("kiri.tools", (root, exports) => {
|
||||
|
||||
const { Vector3, Quaternion } = THREE;
|
||||
const { kiri, moto } = root;
|
||||
|
|
|
|||
117
src/kiri/ui.js
117
src/kiri/ui.js
|
|
@ -10,6 +10,7 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
|
||||
let DOC = self.document,
|
||||
inputAction = null,
|
||||
lastAddTo = null,
|
||||
lastGroup = null,
|
||||
lastDiv = null,
|
||||
addTo = null,
|
||||
|
|
@ -46,11 +47,13 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
toFloat,
|
||||
isSticky,
|
||||
setSticky,
|
||||
newElement,
|
||||
newBoolean,
|
||||
newButton,
|
||||
newBlank,
|
||||
newDiv,
|
||||
newElement,
|
||||
newExpand,
|
||||
endExpand,
|
||||
newGCode,
|
||||
newGroup,
|
||||
newLabel,
|
||||
|
|
@ -58,8 +61,6 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
newRange,
|
||||
newRow,
|
||||
newSelect,
|
||||
newTable,
|
||||
newTableRow,
|
||||
newText,
|
||||
setGroup,
|
||||
addUnits,
|
||||
|
|
@ -68,6 +69,13 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
prompt,
|
||||
alert,
|
||||
onBlur,
|
||||
setEnabled(el, bool) {
|
||||
if (bool) {
|
||||
el.removeAttribute('disabled');
|
||||
} else {
|
||||
el.setAttribute('disabled','');
|
||||
}
|
||||
},
|
||||
setVisible(el, bool) {
|
||||
kiri.ui.setClass(el, 'hide', !bool);
|
||||
},
|
||||
|
|
@ -101,19 +109,26 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
}
|
||||
|
||||
function prompt(message, value) {
|
||||
return confirm(message, {ok:true, cancel:false}, value);
|
||||
return confirm(message, {ok:true, cancel:undefined}, value);
|
||||
}
|
||||
|
||||
function confirm(message, buttons, input, opt = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let { api } = kiri;
|
||||
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 any = $('mod-any');
|
||||
let html = [
|
||||
`<div class="confirm f-col a-stretch">`
|
||||
`<div class="confirm f-col a-stretch" style="padding:5px !important">`
|
||||
];
|
||||
if (message) {
|
||||
html.push(`<label style="user-select:text">${message}</label>`);
|
||||
|
|
@ -122,12 +137,12 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
html = opt.pre.appendAll(html);
|
||||
}
|
||||
let iid;
|
||||
if (typeof(input) === 'string') {
|
||||
iid = `confirm-input-${rnd}`;
|
||||
html.append(`<div><input class="grow" type="text" spellcheck="false" id="${iid}"/></div>`);
|
||||
} else if (Array.isArray(input)) {
|
||||
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) => {
|
||||
|
|
@ -137,10 +152,13 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
if (opt.post) {
|
||||
html.appendAll(opt.post);
|
||||
}
|
||||
$('mod-any').innerHTML = html.join('');
|
||||
dialog.innerHTML = html.join('');
|
||||
function done(value) {
|
||||
kiri.api.modal.hide();
|
||||
setTimeout(() => { resolve(value) }, 150);
|
||||
dialog.close();
|
||||
feature.on_key = onkey_save;
|
||||
if (value !== undefined) {
|
||||
setTimeout(() => { resolve(value) }, 150);
|
||||
}
|
||||
}
|
||||
if (iid) {
|
||||
let array = Array.isArray(input);
|
||||
|
|
@ -162,10 +180,11 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
kiri.api.modal.show('any');
|
||||
dialog.showModal();
|
||||
if (iid) {
|
||||
iid.focus();
|
||||
iid.selectionStart = iid.value.length;
|
||||
iid.selectionStart = 0;
|
||||
iid.selectionEnd = iid.value.length;
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
|
|
@ -267,7 +286,7 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
row,
|
||||
arr,
|
||||
update() {
|
||||
if (hidden[group]) {
|
||||
if (!hidden[group]) {
|
||||
arr.innerHTML = '<i class="fa-solid fa-caret-down"></i>';
|
||||
row.classList.add('hidden');
|
||||
} else {
|
||||
|
|
@ -286,8 +305,8 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
return row;
|
||||
}
|
||||
|
||||
function addCollapsableElement(parent) {
|
||||
let row = newDiv();
|
||||
function addCollapsableElement(parent, options = {}) {
|
||||
let row = newDiv(options);
|
||||
if (parent) parent.appendChild(row);
|
||||
if (lastGroup) lastGroup.push(row);
|
||||
return row;
|
||||
|
|
@ -393,16 +412,42 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
}
|
||||
|
||||
function newDiv(opt = {}) {
|
||||
let div = DOC.createElement('div');
|
||||
let div = DOC.createElement(opt.tag || 'div');
|
||||
addModeControls(div, opt);
|
||||
(opt.addto || addTo).appendChild(div);
|
||||
if (opt.addto) lastDiv = addTo = div;
|
||||
if (opt.addto && opt.class) div.setAttribute('class', opt.class);
|
||||
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;
|
||||
}
|
||||
|
|
@ -421,10 +466,11 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
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) {
|
||||
|
|
@ -442,9 +488,6 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
rows.forEach(row => {
|
||||
cols = Math.max(cols, row.length);
|
||||
});
|
||||
txt.setAttribute("cols", Math.max(30, cols + 1));
|
||||
txt.setAttribute("rows", 6);
|
||||
|
||||
let showing = btn === lastBtn;
|
||||
if (lastTxt) {
|
||||
lastTxt.classList.remove('txt-sel');
|
||||
|
|
@ -462,11 +505,8 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
addModeControls(btn, opt);
|
||||
if (opt.title) {
|
||||
btn.setAttribute("title", options.title);
|
||||
}
|
||||
txt.button = btn;
|
||||
|
||||
return txt;
|
||||
}
|
||||
|
|
@ -794,25 +834,4 @@ gapp.register("kiri.ui", [], (root, exports) => {
|
|||
return row;
|
||||
}
|
||||
|
||||
function newRowTable(array) {
|
||||
let div = newDiv();
|
||||
div.setAttribute("class", "table-row");
|
||||
array.forEach(function(c) {
|
||||
div.appendChild(c);
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
||||
function newTableRow(arrayOfArrays, options) {
|
||||
return newRow(newTable(arrayOfArrays), options);
|
||||
}
|
||||
|
||||
function newTable(arrayOfArrays) {
|
||||
let array = [];
|
||||
for (let i=0; i<arrayOfArrays.length; i++) {
|
||||
array.push(newRowTable(arrayOfArrays[i]));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// use: load.file
|
||||
// use: kiri.selection
|
||||
// use: kiri.platform
|
||||
gapp.register("kiri.widgets", [], (root, exports) => {
|
||||
gapp.register("kiri.widgets", (root, exports) => {
|
||||
|
||||
const { data, kiri, moto, noop } = root;
|
||||
const { api, consts, utils, newWidget, Widget } = kiri;
|
||||
|
|
@ -93,7 +93,7 @@ function opacity(value) {
|
|||
}
|
||||
|
||||
// extend API (api.widgets)
|
||||
const widgets = api.widgets = {
|
||||
const widgets = Object.assign(api.widgets, {
|
||||
load: Widget.loadFromCatalog,
|
||||
new: newWidget,
|
||||
map,
|
||||
|
|
@ -110,6 +110,6 @@ const widgets = 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] }
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,6 +65,16 @@ function transform(def, mesh) {
|
|||
return pos.array;
|
||||
}
|
||||
|
||||
/** find matching attribute regardless of namespace */
|
||||
function getLocalAttribute(node, name) {
|
||||
for (let attr of node.attributes) {
|
||||
if (attr.localName === name) {
|
||||
return attr.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function loadModel(doc) {
|
||||
let items = [];
|
||||
let objects = {};
|
||||
|
|
@ -118,13 +128,16 @@ function loadModel(doc) {
|
|||
query(node, ["components","+component"], (type, node) => {
|
||||
object.components.push({
|
||||
oid: node.getAttribute('objectid'),
|
||||
xform: node.getAttribute('transform')
|
||||
path: getLocalAttribute(node, "path"),
|
||||
xform: node.getAttribute('transform'),
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return resolve({ objects, items });
|
||||
|
||||
// create object mesh from components
|
||||
for (let object of Object.values(objects)) {
|
||||
let { mesh, components } = object;
|
||||
|
|
@ -135,7 +148,9 @@ function loadModel(doc) {
|
|||
for (let component of components) {
|
||||
let { oid, xform } = component;
|
||||
let ref = objects[oid];
|
||||
if (xform) {
|
||||
if (!ref) {
|
||||
console.log({ missing_ref: oid, objects, components, doc });
|
||||
} else if (xform) {
|
||||
mesh.appendAll(transform(xform, ref.mesh));
|
||||
} else {
|
||||
mesh.appendAll(ref.mesh);
|
||||
|
|
@ -160,21 +175,73 @@ function loadModel(doc) {
|
|||
});
|
||||
}
|
||||
|
||||
function extractItems(records) {
|
||||
let outItems = [];
|
||||
let models = Object.values(records);
|
||||
|
||||
// create object mesh from components
|
||||
for (let model of models) {
|
||||
let { objects } = model;
|
||||
for (let object of Object.values(objects)) {
|
||||
let { mesh, components } = object;
|
||||
if (mesh) {
|
||||
continue;
|
||||
}
|
||||
mesh = object.mesh = [];
|
||||
for (let component of components) {
|
||||
let { oid, path, xform } = component;
|
||||
let omap = objects;
|
||||
if (path) {
|
||||
omap = records[path.substring(1)].objects;
|
||||
// console.log({ component_from_ob_path: path, using: omap });
|
||||
}
|
||||
let ref = omap[oid];
|
||||
if (!ref) {
|
||||
console.log({ missing_ref: oid, objects, components });
|
||||
} else if (xform) {
|
||||
mesh.appendAll(transform(xform, ref.mesh));
|
||||
} else {
|
||||
mesh.appendAll(ref.mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create export items from object references
|
||||
for (let model of models) {
|
||||
let { items, objects } = model;
|
||||
for (let item of items || []) {
|
||||
let { oid, xform } = item;
|
||||
let { name, mesh } = objects[oid];
|
||||
item.name = name;
|
||||
if (xform) {
|
||||
item.faces = transform(xform, mesh);
|
||||
} else {
|
||||
item.faces = mesh;
|
||||
}
|
||||
}
|
||||
outItems.push(...items);
|
||||
}
|
||||
|
||||
return outItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} data binary file
|
||||
* @returns {Array} vertex face array
|
||||
*/
|
||||
function parseAsync(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
JSZip.loadAsync(data).then(zip => {
|
||||
for (let [key,value] of Object.entries(zip.files)) {
|
||||
if (key.indexOf(".model") > 0) {
|
||||
value.async("string").then(xml => {
|
||||
resolve(loadModel(new DOMParser().parseFromString(xml, "text/xml")));
|
||||
});
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let zip = await JSZip.loadAsync(data);
|
||||
let models = {};
|
||||
for (let [key, value] of Object.entries(zip.files)) {
|
||||
if (key.endsWith(".model")) {
|
||||
let xml = await value.async("string");
|
||||
let { objects, items } = await loadModel(new DOMParser().parseFromString(xml, "text/xml"));
|
||||
models[key] = { objects, items };
|
||||
}
|
||||
});
|
||||
}
|
||||
resolve(extractItems(models));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
108
src/load/svg.js
108
src/load/svg.js
|
|
@ -22,11 +22,11 @@ function parse(text, opt = { }) {
|
|||
const rez = (opt.resolution || 1);
|
||||
const dpi = (opt.dpi || 0);
|
||||
const segmin = Math.max(1, opt.segmin || 10);
|
||||
const objs = [];
|
||||
const data = new THREE.SVGLoader().parse(text);
|
||||
const paths = data.paths;
|
||||
const xmlat = data.xml.attributes;
|
||||
const polys = fromSoup ? [] : undefined;
|
||||
const objs = [];
|
||||
const polys = [];
|
||||
const isinch = xmlat.width?.value.endsWith('in');
|
||||
const scale = isinch ? 25.4 : (dpi ? 1 / (dpi / 25.4) : 1);
|
||||
const depth = parseFloat(opt.depth || xmlat['data-km-extrude']?.value
|
||||
|
|
@ -39,76 +39,50 @@ function parse(text, opt = { }) {
|
|||
let type = path.userData?.node?.nodeName;
|
||||
let width = path.userData?.style?.strokeWidth;
|
||||
let miter = path.userData?.style?.strokeMiterLimit;
|
||||
if (fromSoup) {
|
||||
for (let sub of path.subPaths) {
|
||||
let points = sub.curves.map(curve => {
|
||||
let length = curve.getLength();
|
||||
let segs = curve.type === 'LineCurve' ?
|
||||
1 : Math.max(Math.ceil(length * rez), segmin);
|
||||
return curve.getPoints(segs);
|
||||
}).flat();
|
||||
if (points.length < 3) {
|
||||
// console.log({ sub, length, points });
|
||||
continue;
|
||||
}
|
||||
let poly = base.newPolygon().addPoints(points.map(p => base.newPoint(p.x, -p.y, 0)));
|
||||
if (poly.appearsClosed()) poly.points.pop();
|
||||
if (type === 'polyline') poly.setOpen(true);
|
||||
poly._svg = { width, miter };
|
||||
polys.push(poly);
|
||||
if (scale !== 1) {
|
||||
poly.scale({ x: scale, y: scale, z: 1 });
|
||||
}
|
||||
for (let sub of path.subPaths) {
|
||||
let points = sub.curves.map(curve => {
|
||||
let length = curve.getLength();
|
||||
let segs = curve.type === 'LineCurve' ?
|
||||
1 : Math.max(Math.ceil(length * rez), segmin);
|
||||
return curve.getPoints(segs);
|
||||
}).flat();
|
||||
if (points.length < 3) {
|
||||
// console.log({ sub, length, points });
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (justPoly) {
|
||||
continue;
|
||||
}
|
||||
let geom = new THREE.ExtrudeGeometry(shapes, {
|
||||
depth,
|
||||
steps: 1,
|
||||
bevelEnabled: false
|
||||
});
|
||||
let array = geom.attributes.position.array;
|
||||
// invert y
|
||||
for (let i=1; i<array.length; i+=3) {
|
||||
array[i] = -array[i];
|
||||
}
|
||||
// invert vertex order to compensate for inverted y
|
||||
for (let i=0; i<array.length; i+=9) {
|
||||
let tmp = array.slice(i,i+3);
|
||||
for (let j=0; j<3; j++) {
|
||||
array[i+j] = array[i+j+3];
|
||||
array[i+j+3] = tmp[j];
|
||||
let poly = base.newPolygon().addPoints(points.map(p => base.newPoint(p.x, -p.y, 0)));
|
||||
if (poly.appearsClosed()) poly.points.pop();
|
||||
if (type === 'polyline') poly.setOpen(true);
|
||||
poly._svg = { width, miter };
|
||||
polys.push(poly);
|
||||
if (scale !== 1) {
|
||||
poly.scale({ x: scale, y: scale, z: 1 });
|
||||
}
|
||||
}
|
||||
objs.push([ ...array ]);
|
||||
}
|
||||
|
||||
if (fromSoup) {
|
||||
const nest = base.polygons.nest(polys.filter(p => {
|
||||
// filter duplicates
|
||||
for (let pc of polys) {
|
||||
if (pc === p) {
|
||||
return true;
|
||||
} else {
|
||||
return !pc.isEquivalent(p);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (justPoly) {
|
||||
return nest;
|
||||
}
|
||||
|
||||
for (let poly of nest) {
|
||||
let obj = poly.extrude(depth);
|
||||
objs.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return justPoly ? polys : objs;
|
||||
const sub = fromSoup ? base.polygons.nest(polys) : polys;
|
||||
const nest = sub.filter(p => {
|
||||
// filter duplicates
|
||||
for (let pc of polys) {
|
||||
if (pc === p) {
|
||||
return true;
|
||||
} else {
|
||||
return !pc.isEquivalent(p);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (justPoly) {
|
||||
return nest;
|
||||
}
|
||||
|
||||
for (let poly of nest) {
|
||||
let obj = poly.extrude(depth);
|
||||
objs.push(obj);
|
||||
}
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ let mods = [];
|
|||
|
||||
gapp.overlay = Object.assign;
|
||||
|
||||
// extract function arguments grouped by type
|
||||
function exargs(args) {
|
||||
return {
|
||||
funcs: args.filter(a => typeof a === 'function'),
|
||||
|
|
@ -70,34 +71,38 @@ function exargs(args) {
|
|||
};
|
||||
}
|
||||
|
||||
// register module without a load function
|
||||
gapp.register = function() {
|
||||
// console.log(1, { mods });
|
||||
const args = exargs([...arguments]);
|
||||
const name = args.strings[0];
|
||||
const fn = args.funcs[0];
|
||||
const mod = { fn, name };
|
||||
// console.log(2, { mods });
|
||||
// console.log('reg', { mod, mods });
|
||||
mods.push(mod);
|
||||
};
|
||||
|
||||
// prevent module load from terminating main/init chain
|
||||
function safeFN(fn, name) {
|
||||
return function() {
|
||||
try {
|
||||
return fn(...arguments);
|
||||
} catch (error) {
|
||||
console.log({ register_fail: name, error });
|
||||
if (error.stack) {
|
||||
console.log(`[${name}]`, error.stack);
|
||||
} else {
|
||||
console.log({ register_fail: name, error });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// register module without a load function
|
||||
gapp.register = function() {
|
||||
const args = exargs([...arguments]);
|
||||
const objs = args.objects || {};
|
||||
const name = objs.name || objs.module || args.strings[0];
|
||||
const fn = objs.exec || args.funcs[0];
|
||||
const mod = { fn, name };
|
||||
mods.push(mod);
|
||||
};
|
||||
|
||||
// perform dependency checks and run module load functions
|
||||
gapp.main = function() {
|
||||
const args = exargs([...arguments]);
|
||||
const app = args.strings[0];
|
||||
const post = args.funcs[0];
|
||||
const pre = args.funcs[1];
|
||||
const objs = args.objects[0] || {};
|
||||
const app = objs.app || args.strings[0];
|
||||
const post = objs.post || args.funcs[0];
|
||||
const pre = objs.pre || args.funcs[1];
|
||||
const root = self;
|
||||
// optional fn to run before loading
|
||||
if (pre) {
|
||||
|
|
@ -129,6 +134,7 @@ gapp.main = function() {
|
|||
}
|
||||
let tmp = path[map] || {};
|
||||
safeFN(fn, name)(root, exports => {
|
||||
// map exports into module namespace when called
|
||||
if (exports) {
|
||||
return path[map] = Object.assign(tmp, exports);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,37 +2,40 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
gapp.main("main.kiri", [], (root) => {
|
||||
gapp.main({
|
||||
app: "kiri",
|
||||
|
||||
const { kiri } = root;
|
||||
const { api } = kiri;
|
||||
pre(root) {
|
||||
let mods = root.kirimod = ( root.kirimod || [] );
|
||||
|
||||
// complete module loading
|
||||
kiri.load_exec();
|
||||
|
||||
}, (root) => {
|
||||
|
||||
const modfns = root.kirimod = ( root.kirimod || [] );
|
||||
|
||||
const kiri = root.kiri = {
|
||||
beta: 0,
|
||||
driver: {}, // driver modules
|
||||
load(fn) {
|
||||
modfns.push(fn);
|
||||
},
|
||||
load_exec(api) {
|
||||
const saferun = (fn) => {
|
||||
try {
|
||||
fn(api || kiri.api);
|
||||
} catch (error) {
|
||||
console.log({ module_error: error });
|
||||
}
|
||||
};
|
||||
// complete module loading
|
||||
modfns.forEach(modfn => saferun(modfn));
|
||||
// rewrite load() to be immediate post-finalize
|
||||
kiri.load = (modfn) => saferun(modfn);
|
||||
}
|
||||
};
|
||||
let kiri = root.kiri = {
|
||||
beta: 4113,
|
||||
driver: {
|
||||
// attached driver modules
|
||||
},
|
||||
load(fn) {
|
||||
// modules register exec() functions
|
||||
mods.push(fn);
|
||||
},
|
||||
load_exec(api) {
|
||||
// process all module exec() functions
|
||||
const saferun = (fn) => {
|
||||
try {
|
||||
fn(api || kiri.api);
|
||||
} catch (error) {
|
||||
console.log({ module_error: error });
|
||||
}
|
||||
};
|
||||
// complete module loading
|
||||
mods.forEach(fn => saferun(fn));
|
||||
// rewrite load() to run immediately post-finalize
|
||||
kiri.load = (fn) => saferun(fn);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
post(root) {
|
||||
// complete module loading
|
||||
root.kiri.load_exec();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -280,6 +280,11 @@ function space_init(data) {
|
|||
},
|
||||
'keydown', evt => {
|
||||
let { shiftKey, metaKey, ctrlKey, code } = evt;
|
||||
let once = keyOnce[code];
|
||||
if (once) {
|
||||
delete keyOnce[code];
|
||||
return once(evt);
|
||||
}
|
||||
let rv = (Math.PI / 12);
|
||||
if (api.modal.showing) {
|
||||
if (code === 'Escape') {
|
||||
|
|
@ -598,6 +603,16 @@ function space_load(data) {
|
|||
}
|
||||
|
||||
let metaCache = {};
|
||||
let keyOnce = {};
|
||||
|
||||
function key_once(data) {
|
||||
const { code, fn } = data;
|
||||
keyOnce[code] = fn;
|
||||
}
|
||||
|
||||
function key_once_cancel(code) {
|
||||
delete keyOnce[code];
|
||||
}
|
||||
|
||||
function store_meta() {
|
||||
mesh.db.admin.put("meta", metaCache);
|
||||
|
|
@ -711,6 +726,8 @@ function set_snap_value(snap) {
|
|||
|
||||
// bind functions to topics
|
||||
broker.listeners({
|
||||
key_once,
|
||||
key_once_cancel,
|
||||
load_files,
|
||||
object_meta,
|
||||
object_destroy,
|
||||
|
|
|
|||
|
|
@ -9,15 +9,19 @@
|
|||
"use strict";
|
||||
|
||||
// dep: moto.space
|
||||
// dep: moto.broker
|
||||
gapp.register("mesh.split", [], (root, exports) => {
|
||||
|
||||
const { broker } = gapp;
|
||||
const { Mesh, MeshPhongMaterial, PlaneGeometry, DoubleSide, Vector3 } = THREE;
|
||||
const { mesh, moto } = root;
|
||||
const { space } = moto;
|
||||
const { api } = mesh;
|
||||
|
||||
let isActive;
|
||||
|
||||
const key_once = broker.bind('key_once');
|
||||
const key_cancel = broker.bind('key_once_cancel');
|
||||
|
||||
// split functions
|
||||
let split = {
|
||||
active() {
|
||||
|
|
@ -29,6 +33,11 @@ let split = {
|
|||
return;
|
||||
}
|
||||
let { api, util } = mesh;
|
||||
let { log } = api;
|
||||
if (api.selection.models().length === 0) {
|
||||
log.emit('no models selected for splitting');
|
||||
return;
|
||||
}
|
||||
// create split plane visual
|
||||
let geo, mat, obj = new Mesh(
|
||||
geo = new PlaneGeometry(1,1),
|
||||
|
|
@ -72,11 +81,36 @@ let split = {
|
|||
obj.position.set(mid.x, y, -mid.y);
|
||||
});
|
||||
isActive = true;
|
||||
key_once({ code: 'KeyS', fn(evt) {
|
||||
split.select();
|
||||
}});
|
||||
key_once({ code: 'KeyV', fn(evt) {
|
||||
evt.preventDefault();
|
||||
let state = split.state;
|
||||
split.end();
|
||||
function doit(z) {
|
||||
state.plane = { z };
|
||||
split.select(state);
|
||||
}
|
||||
api.modal.dialog({
|
||||
title: "split object Z",
|
||||
body: [ h.div({ class: "additem" }, [
|
||||
h.label('Z value'),
|
||||
h.input({ value: 0, size: 5, id: "_value" }),
|
||||
h.button({ _: "split", onclick() {
|
||||
const { _value } = api.modal.bound;
|
||||
doit(parseFloat(_value.value));
|
||||
api.modal.hide();
|
||||
} })
|
||||
]) ]
|
||||
});
|
||||
api.modal.bound._value.focus();
|
||||
} });
|
||||
},
|
||||
|
||||
select() {
|
||||
select(state) {
|
||||
let { log } = mesh.api;
|
||||
let { models, plane } = split.state;
|
||||
let { models, plane } = split.state || state;
|
||||
if (!(models && plane)) {
|
||||
return split.end();
|
||||
}
|
||||
|
|
@ -92,6 +126,8 @@ let split = {
|
|||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
key_cancel('KeyS');
|
||||
key_cancel('KeyV');
|
||||
let space = moto.space;
|
||||
let { obj } = split.state;
|
||||
space.scene.remove(obj);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
let terms = {
|
||||
COPYRIGHT: "Copyright (C) Stewart Allen <sa@grid.space> - All Rights Reserved",
|
||||
LICENSE: "See the license.md file included with the source distribution",
|
||||
VERSION: (is_self ? self : this).debug_version || "4.0.31"
|
||||
VERSION: (is_self ? self : this).debug_version || "4.1.0"
|
||||
};
|
||||
|
||||
if (typeof(module) === 'object') {
|
||||
|
|
|
|||
|
|
@ -1308,10 +1308,39 @@ gapp.register("moto.space", [], (root, exports) => {
|
|||
return {
|
||||
url: ncv.toDataURL(param.format || "image/png", param.options),
|
||||
width: ncv.width,
|
||||
height: ncv.height
|
||||
height: ncv.height,
|
||||
};
|
||||
},
|
||||
|
||||
screenshot3(param = {}) {
|
||||
let oco = renderer.domElement;
|
||||
let oWidth = oco.offsetWidth;
|
||||
let oHeight = oco.offsetHeight;
|
||||
let oRatio = oWidth / oHeight;
|
||||
let width = param.width || 512;
|
||||
let height = param.height || width;
|
||||
let nRatio = width / height;
|
||||
let ncv = document.createElement('canvas');
|
||||
ncv.width = width;
|
||||
ncv.height = height;
|
||||
let nco = ncv.getContext('2d');
|
||||
let ox = 0, oy = 0;
|
||||
if (oRatio > nRatio) {
|
||||
let tmp = oWidth;
|
||||
oWidth = oHeight * nRatio;
|
||||
ox = (tmp - oWidth) / 2;
|
||||
} else {
|
||||
let tmp = oHeight;
|
||||
oHeight = oWidth * nRatio;
|
||||
oy = (tmp - oHeight) / 2;
|
||||
}
|
||||
nco.drawImage(oco, ox, oy, oWidth, oHeight, 0, 0, width, height);
|
||||
if (param.out) {
|
||||
ncv.toBlob(blob => blob.arrayBuffer().then(png => param.out({ png, width, height })));
|
||||
}
|
||||
return ncv;
|
||||
},
|
||||
|
||||
internals: () => {
|
||||
return { renderer, camera, platform };
|
||||
},
|
||||
|
|
|
|||
|
|
@ -33,9 +33,14 @@ function build(data, context) {
|
|||
html.push(`<${type}`);
|
||||
let func = {};
|
||||
for (let [key, val] of Object.entries(attr || {})) {
|
||||
if (val === undefined) {
|
||||
continue;
|
||||
}
|
||||
let tov = typeof val;
|
||||
if (key === '_') {
|
||||
text = val;
|
||||
} else if (key.startsWith('_')) {
|
||||
if (val) html.push(` ${key.substring(1)}`);
|
||||
} else if (key === 'id') {
|
||||
elid = val ? (tov === 'object' ? val.join('_') : val) : undefined;
|
||||
} else if (tov === 'function') {
|
||||
|
|
@ -76,7 +81,12 @@ let h = exports({
|
|||
bind: (el, data, opt = {}) => {
|
||||
let ctx = [];
|
||||
let html = build(data, ctx).join('');
|
||||
if (opt.append) {
|
||||
if (opt.after || opt.before) {
|
||||
let tmpl = document.createElement('template');
|
||||
tmpl.innerHTML = html;
|
||||
opt.before && el.before(tmpl.content);
|
||||
opt.after && el.after(tmpl.content);
|
||||
} else if (opt.append) {
|
||||
el.innerHTML += html;
|
||||
} else {
|
||||
el.innerHTML = html;
|
||||
|
|
@ -149,7 +159,7 @@ gapp.overlay(root, {
|
|||
// add common element types
|
||||
[
|
||||
"a", "i", "hr", "div", "pre", "code", "span", "label", "input",
|
||||
"button", "svg", "textarea", "select", "option"
|
||||
"button", "svg", "textarea", "select", "option", "img", "canvas"
|
||||
].forEach(type => {
|
||||
h[type] = (attr, innr) => {
|
||||
return h.el(type, attr, innr);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
.pop-lcol svg {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
#mod-x {
|
||||
.mod-x {
|
||||
position: absolute;
|
||||
font-size: 30px;
|
||||
top: 8px;
|
||||
|
|
@ -107,7 +107,7 @@ button {
|
|||
cursor: pointer;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
button:hover {
|
||||
button:not([disabled]):hover {
|
||||
background-color: var(--main-color);
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -118,6 +118,9 @@ button[load] {
|
|||
button[del] {
|
||||
margin-left: 5px;
|
||||
}
|
||||
button[disabled] {
|
||||
color: #999;
|
||||
}
|
||||
input {
|
||||
background-color: #f8f8ff;
|
||||
margin-bottom: 1px;
|
||||
|
|
@ -183,6 +186,37 @@ th, tr, td, span, div, label, button {
|
|||
}
|
||||
th, tr, td, label {
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
}
|
||||
details summary {
|
||||
list-style: none;
|
||||
}
|
||||
details summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
details summary::before {
|
||||
content: "";
|
||||
}
|
||||
details summary {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
details summary::after {
|
||||
content: "▸";
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
font-size: 1em;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
details[open] summary::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* for bambu mgr dialog */
|
||||
.video {
|
||||
border: 1px solid #888;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* container for entire page / app */
|
||||
|
|
@ -201,6 +235,9 @@ th, tr, td, label {
|
|||
}
|
||||
|
||||
/** dark conversion */
|
||||
.dark button[disabled] {
|
||||
color: #888;
|
||||
}
|
||||
.dark input[disabled] {
|
||||
border-color: #666;
|
||||
background-color: #444;
|
||||
|
|
@ -223,8 +260,12 @@ th, tr, td, label {
|
|||
}
|
||||
.dark button {
|
||||
color: #fff;
|
||||
background-color: #777;
|
||||
border: 1px solid #999;
|
||||
background-color: #555;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
.dark button:not([disabled]):hover {
|
||||
background-color: var(--blue-2);
|
||||
color: black;
|
||||
}
|
||||
.dark #progress {
|
||||
background-color: #333;
|
||||
|
|
@ -295,10 +336,15 @@ th, tr, td, label {
|
|||
/* color: #fff; */
|
||||
background-color: var(--blue-1);
|
||||
}
|
||||
.dark #modal {
|
||||
.dark #dialog {
|
||||
background-color: rgba(60,60,60,0.85);
|
||||
border-top: 8px solid rgba(100,100,100,1);
|
||||
border-bottom: 7px solid rgba(100,100,100,1);
|
||||
}
|
||||
.dark #modal, .dark #dialog {
|
||||
color: #fff !important;
|
||||
}
|
||||
.dark #modal select {
|
||||
.dark #modal select, .dark #dialog select {
|
||||
color: #fff !important;
|
||||
}
|
||||
.dark .t-body,
|
||||
|
|
@ -585,6 +631,15 @@ th, tr, td, label {
|
|||
.bt2 {
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
.bred {
|
||||
border-color: red !important;
|
||||
}
|
||||
.checker {
|
||||
background:
|
||||
linear-gradient(135deg, transparent 45%, #555 45%, #555 55%, transparent 55%) !important;
|
||||
background-size: 10px 10px;
|
||||
background-position: 0 0, 5px 5px;
|
||||
}
|
||||
.contents {
|
||||
display: contents;
|
||||
}
|
||||
|
|
@ -594,6 +649,15 @@ th, tr, td, label {
|
|||
.noshow {
|
||||
display: none;
|
||||
}
|
||||
.mono {
|
||||
font-family: monospace;
|
||||
}
|
||||
.font-smol {
|
||||
font-size: smaller;
|
||||
}
|
||||
.font-tiny {
|
||||
font-size: x-small;
|
||||
}
|
||||
.gap1 {
|
||||
gap: 1px;
|
||||
}
|
||||
|
|
@ -612,21 +676,39 @@ th, tr, td, label {
|
|||
.gap10 {
|
||||
gap: 10px;
|
||||
}
|
||||
.pad3 {
|
||||
padding: 3px !important;
|
||||
}
|
||||
.pad4 {
|
||||
padding: 4px !important;
|
||||
}
|
||||
.pad5 {
|
||||
padding: 5px !important;
|
||||
}
|
||||
.h100 {
|
||||
height: 100%;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
.frow {
|
||||
.frow, .f-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
.f-col {
|
||||
.fcol, .f-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.f-grow > * {
|
||||
flex: 1;
|
||||
}
|
||||
.auto {
|
||||
flex-basis: auto;
|
||||
}
|
||||
.basis-50 {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
.grow0 {
|
||||
flex-grow: 0 !important;
|
||||
}
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
@ -654,15 +736,24 @@ th, tr, td, label {
|
|||
.j-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.j-stretch {
|
||||
justify-items: stretch;
|
||||
}
|
||||
.as-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
.space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.center {
|
||||
.center, .t-center {
|
||||
text-align: center;
|
||||
}
|
||||
.t-left {
|
||||
text-align: left;
|
||||
}
|
||||
.t-just {
|
||||
text-align: justify;
|
||||
}
|
||||
|
|
@ -697,6 +788,7 @@ th, tr, td, label {
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
color: black;
|
||||
background-color: #fff;
|
||||
font-family: 'Russo One', sans-serif;
|
||||
z-index: 1000;
|
||||
|
|
@ -865,15 +957,21 @@ th, tr, td, label {
|
|||
|
||||
.slideshow #panel-left .settings {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.15s ease-in-out;
|
||||
transition: transform 0.1s ease-in-out;
|
||||
transition-delay: 0.3s;
|
||||
}
|
||||
.slideshow #panel-left:hover .settings {
|
||||
transform: translateX(0);
|
||||
transition-delay: 0.0s;
|
||||
}
|
||||
.slideshow #panel-left:hover #slide-show {
|
||||
left: -100%;
|
||||
transform: translateX(-100%);
|
||||
transition-delay: 0.0s;
|
||||
}
|
||||
.slideshow #slide-show {
|
||||
transition-delay: 0.5s;
|
||||
transform: translateX(-80%);
|
||||
/* transform: translateX(0); */
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
|
|
@ -907,6 +1005,9 @@ th, tr, td, label {
|
|||
#panel-right * {
|
||||
direction: ltr;
|
||||
} */
|
||||
#panel-left {
|
||||
padding-right: 5px;
|
||||
}
|
||||
#panel-left, #panel-right {
|
||||
max-height: 100%;
|
||||
background-color: var(--work-area);
|
||||
|
|
@ -914,7 +1015,7 @@ th, tr, td, label {
|
|||
#panel-left, #panel-right, #modal {
|
||||
pointer-events: visible;
|
||||
}
|
||||
#panel-left label, #panel-left svg {
|
||||
#panel-left label, #panel-left svg, button svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
#ws-widgets button {
|
||||
|
|
@ -1443,6 +1544,23 @@ th, tr, td, label {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
#dialog {
|
||||
position: fixed;
|
||||
outline: none;
|
||||
color: black;
|
||||
top: 100px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: rgba(255,255,255,0.85);
|
||||
border: 0;
|
||||
border-top: 8px solid rgba(180,180,180,1);
|
||||
border-bottom: 7px solid rgba(180,180,180,1);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
|
@ -1485,11 +1603,11 @@ th, tr, td, label {
|
|||
#tool-cols {
|
||||
max-height: 350px;
|
||||
}
|
||||
.dark #mod-x {
|
||||
.dark .mod-x {
|
||||
border-color: #555;
|
||||
background-color: rgba(0,0,0,0.15);
|
||||
}
|
||||
#mod-x {
|
||||
.mod-x {
|
||||
border-radius: 5px;
|
||||
border: 2px solid rgba(255,255,255,0.5);
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
|
|
@ -1498,7 +1616,7 @@ th, tr, td, label {
|
|||
bottom: -20px;
|
||||
right: 0;
|
||||
}
|
||||
#mod-x svg {
|
||||
.mod-x svg {
|
||||
aspect-ratio: 1 / 1 !important;
|
||||
}
|
||||
#mod-setup {
|
||||
|
|
@ -1543,6 +1661,9 @@ th, tr, td, label {
|
|||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.dark #mod-help a:hover {
|
||||
color: #333;
|
||||
}
|
||||
#mod-help a:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
|
@ -1580,7 +1701,7 @@ th, tr, td, label {
|
|||
background-color: rgba(255,255,255,0.5);
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 3px;
|
||||
padding: 20px 10px 10px 10px;
|
||||
padding: 12px 10px 10px 10px;
|
||||
}
|
||||
.mod-print .box select {
|
||||
min-width: 10em;
|
||||
|
|
@ -1642,10 +1763,13 @@ th, tr, td, label {
|
|||
display: none;
|
||||
}
|
||||
#code-preview-textarea {
|
||||
width: 40em;
|
||||
height: 12em;
|
||||
font-size: smaller;
|
||||
min-width: 50em;
|
||||
width: 100%;
|
||||
height: 7em;
|
||||
font-size: x-small;
|
||||
font-family: Courier,monospace;
|
||||
white-space: nowrap;
|
||||
resize: none;
|
||||
}
|
||||
.mod-end {
|
||||
height: 10px;
|
||||
|
|
@ -1855,6 +1979,13 @@ th, tr, td, label {
|
|||
.t-33 {
|
||||
width: 33%;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.fat5 {
|
||||
display: inline-block;
|
||||
min-width: 5px !important;
|
||||
}
|
||||
.t-group {
|
||||
background-image: var(--gradbar);
|
||||
white-space: nowrap;
|
||||
|
|
@ -1866,7 +1997,7 @@ th, tr, td, label {
|
|||
color: white;
|
||||
}
|
||||
.t-body {
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
background-color: rgba(255,255,255,1);
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
|
@ -2136,10 +2267,10 @@ th, tr, td, label {
|
|||
.settings .set-header a {
|
||||
font-size: smaller;
|
||||
}
|
||||
#dev-sel {
|
||||
margin-bottom: 0;
|
||||
#dev-sel, .dev-sel {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
#dev-list {
|
||||
#dev-list, .dev-list {
|
||||
align-self: stretch;
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
|
@ -2245,6 +2376,7 @@ th, tr, td, label {
|
|||
.var-row {
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.var-row label {
|
||||
padding: 2px 4px 2px 0;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta http-equiv="origin-trial" content="Ahn06WzHCdvTPdfl/C5eFBmA8rzzgUqWvppp+iV8SDp0jvr5DLgf8XMeAajujwLCs/6LgRoEImoJncgvG9ox8AsAAAB0eyJvcmlnaW4iOiJodHRwczovL2dyaWQuc3BhY2U6NDQzIiwiZmVhdHVyZSI6IlVucmVzdHJpY3RlZFNoYXJlZEFycmF5QnVmZmVyIiwiZXhwaXJ5IjoxNzM5OTIzMTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZX0=">
|
||||
<meta http-equiv="origin-trial" content="Aixo1di7jfA+ystPFy70vGYyODYVAju1y8EQWwdkdtl5sB7i990oDCnqLQntpfl7NsW74wz99Og7xgQqeLKiBQsAAAB0eyJvcmlnaW4iOiJodHRwczovL2dyaWQuc3BhY2U6NDQzIiwiZmVhdHVyZSI6IlVucmVzdHJpY3RlZFNoYXJlZEFycmF5QnVmZmVyIiwiZXhwaXJ5IjoxNzUzMTQyNDAwLCJpc1N1YmRvbWFpbiI6dHJ1ZX0=">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||
<title>Kiri:Moto</title>
|
||||
<link rel="icon" href="/kiri/favicon.ico">
|
||||
|
|
@ -61,12 +61,16 @@
|
|||
<div class="grow"></div>
|
||||
<div id="app-name" class="f-col a-center top-menu">
|
||||
<span class="grow">
|
||||
<span class="km-font">Kiri:Moto</span>
|
||||
<span id="app-name-text" class="km-font">Kiri:Moto</span>
|
||||
<div id="app-name-pop" class="top-menu-drop top-menu-center">
|
||||
<div class="content">
|
||||
<div id="app-help">
|
||||
<label lk="help">help</label>
|
||||
</div>
|
||||
<hr width="100%">
|
||||
<div id="app-don8">
|
||||
<label lk="donate">donate</label>
|
||||
</div>
|
||||
<div>
|
||||
<label id="app-mesh">mesh edit</label>
|
||||
</div>
|
||||
|
|
@ -392,8 +396,10 @@
|
|||
<div><label>Profile</label><span id="mode-profile"></span></div>
|
||||
</div>
|
||||
<div class="set2-group f-col">
|
||||
<div class="set-header"><a>objects</a></div>
|
||||
<div id="ws-widgets" class="f-col"></div>
|
||||
<details open>
|
||||
<summary class="set-header"><a>objects</a></summary>
|
||||
<div id="ws-widgets" class="f-col"></div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="set2-group f-col mode-fdm">
|
||||
<div class="set-header"><a>ranges</a></div>
|
||||
|
|
@ -441,7 +447,21 @@
|
|||
<div id="modal-box" class="f-col">
|
||||
|
||||
<!-- title bar and closer -->
|
||||
<div class="mod-top f-row"><div id="mod-x"><i class="fas fa-times"></i></div></div>
|
||||
<div class="mod-top f-row"><div id="mod-x" class="mod-x"><i class="fas fa-times"></i></div></div>
|
||||
|
||||
<!-- donate menu -->
|
||||
<div id="mod-don8" class="mdialog f-col">
|
||||
<div class="f-col gap4" style="gap:5px">
|
||||
<div class="t-pad2"></div>
|
||||
<div style="padding:20px;font-size:larger;font-weight:bold">Your contribution is appreciated!</div>
|
||||
<div style="padding:5px 0 5px 0;justify-content:center">And supports ongoing development,</div>
|
||||
<div style="padding:5px 0 20px 0;justify-content:center">servers, forums, and related costs</div>
|
||||
<button id="don8pt" class="yellow" style="justify-content:center;padding:25px;font-size:larger"><a target="_paypal" href="https://www.patreon.com/c/gridspace3d">Become a Patreon Patron</a></button>
|
||||
<button id="don8gh" class="yellow" style="justify-content:center;padding:25px;font-size:larger"><a target="_github" href="https://github.com/sponsors/GridSpace">Sponsor on GitHub</a></button>
|
||||
<button id="don8pp" class="yellow" style="justify-content:center;padding:25px;font-size:larger"><a target="_paypal" href="https://paypal.me/gridspace3d">Donate with PayPal</a></button>
|
||||
<div class="t-pad2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- help menu -->
|
||||
<div id="mod-help" class="mdialog f-col">
|
||||
|
|
@ -455,7 +475,7 @@
|
|||
<a target="_rsc" href="https://discord.gg/suyCCgr"><i class="fab fa-discord"></i> Discord Server</a>
|
||||
<a target="_rsc" href="https://github.com/gridspace/grid-apps/issues"><i class="fas fa-bug"></i> Bug Reports</a>
|
||||
<a target="_rsc" href="https://github.com/gridspace/grid-apps/releases"><i class="fab fa-github"></i> Releases & Binaries</a>
|
||||
<a target="_rsc" href="https://paypal.me/gridspace3d?locale.x=en_US"><i class="fab fa-paypal"></i> Support Development</a>
|
||||
<a target="_rsc" onclick="kiri.api.modal.show('don8')"><i class="fa-solid fa-sack-dollar"></i> Support Development</a>
|
||||
<a href="https://youtu.be/08795Sj22QE" target="youtube"><i class="fab fa-youtube"></i>Video Guide (from v2.5)</a>
|
||||
</div>
|
||||
<hr width="100%">
|
||||
|
|
@ -581,27 +601,45 @@
|
|||
<div id="mod-local" class="mdialog f-col"></div>
|
||||
<!-- export dialogs -->
|
||||
<div id="mod-x-any" class="mod-print mdialog f-col">
|
||||
<div class="header"><label>export</label></div>
|
||||
<div id="print-info" class="f-col box">
|
||||
<div><label>file name</label><input id="print-filename" size="20" spellcheck="false" /></div>
|
||||
<div><label>file size (bytes)</label><input id="print-filesize" size="12" disabled="true" /></div>
|
||||
<div><label>time estimate (h:m:s)</label><input id="output-time" size="12" disabled="true" /></div>
|
||||
<div class="f-row gap4">
|
||||
<div class="f-col grow">
|
||||
<div class="header"><label>job</label></div>
|
||||
<div id="print-info" class="f-col box">
|
||||
<div><label>file name</label><input id="print-filename" size="20" spellcheck="false" /></div>
|
||||
<div><label>file size (bytes)</label><input id="print-filesize" size="12" disabled="true" /></div>
|
||||
<div><label>time estimate (h:m:s)</label><input id="output-time" size="12" disabled="true" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="print-filament" class="f-col">
|
||||
<div class="header"><label>material</label></div>
|
||||
<div class="f-col box">
|
||||
<div><label>filament density (g/cm^3)</label><input id="print-density" size="12" value="1.25" /></div>
|
||||
<div><label>filament used (mm)</label><input id="print-filament" size="12" disabled="true" /></div>
|
||||
<div><label>printed weight (g)</label><input id="print-weight" size="12" disabled="true" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="print-filament-head" class="header"><label>filament</label></div>
|
||||
<div id="print-filament-info" class="f-col box">
|
||||
<div><label>filament density (g/cm^3)</label><input id="print-density" size="12" value="1.25" /></div>
|
||||
<div><label>filament used (mm)</label><input id="print-filament" size="12" disabled="true" /></div>
|
||||
<div><label>printed weight (g)</label><input id="print-weight" size="12" disabled="true" /></div>
|
||||
</div>
|
||||
<div id="code-preview-head" class="header"><label>code preview</label></div>
|
||||
<div id="code-preview-head" class="header"><label>gcode preview</label></div>
|
||||
<div id="code-preview" class="f-col box">
|
||||
<div><textarea id="code-preview-textarea"></textarea></div>
|
||||
</div>
|
||||
<div class="header"><label>gcode</label></div>
|
||||
<div class="header"><label>download</label></div>
|
||||
<div class="f-row box">
|
||||
<button id="print-download" class="grow">download</button>
|
||||
<button id="print-download" class="grow">gcode</button>
|
||||
<button id="print-palette" class="grow">palette</button>
|
||||
<button id="print-zip" class="grow">zip file</button>
|
||||
<button id="print-zip" class="grow">zip</button>
|
||||
<button id="print-3mf" class="grow">3mf</button>
|
||||
</div>
|
||||
<div id="bambu-output" class="f-col">
|
||||
<div class="header"><label>send to bambu</label></div>
|
||||
<div class="f-row box gap4 a-center">
|
||||
<label class="grow0 pad3">printer</label>
|
||||
<select id="print-bambu-device"></select>
|
||||
<label class="grow0 pad3">spool</label>
|
||||
<select id="print-bambu-spool"></select>
|
||||
<button id="print-bambu-1" class="grow" disabled>send</button>
|
||||
<button id="print-bambu-2" class="grow" disabled>send + print</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="send-to-octohead" class="header"><label>octoprint</label></div>
|
||||
<div id="send-to-octoprint" class="f-col box">
|
||||
|
|
@ -643,7 +681,7 @@
|
|||
<div id="mod-x-sla" class="mod-print mdialog f-col">
|
||||
<div class="header"><label>export</label></div>
|
||||
<div id="print-info" class="f-col box">
|
||||
<div><label>file name</label><input id="print-filename" size="20" spellcheck="false" /></div>
|
||||
<div><label>file name</label><input id="print-filename-sla" size="20" spellcheck="false" /></div>
|
||||
<div><label>resin used ML</label><input id="print-volume" size="10" spellcheck="false" /></div>
|
||||
<div><label>layers</label><input id="print-layers" size="10" spellcheck="false" /></div>
|
||||
<div><label>print time</label><input id="print-time" size="10" spellcheck="false" /></div>
|
||||
|
|
@ -656,7 +694,7 @@
|
|||
<div id="mod-x-laser" class="mod-print mdialog f-col">
|
||||
<div class="header"><label>export</label></div>
|
||||
<div id="print-info" class="f-col box">
|
||||
<div><label>file name</label><input id="print-filename" size="20" spellcheck="false" /></div>
|
||||
<div><label>file name</label><input id="print-filename-laser" size="20" spellcheck="false" /></div>
|
||||
<div><label>segments</label><input id="print-lines" size="12" disabled="true" /></div>
|
||||
</div>
|
||||
<div class="header"><label>download</label></div>
|
||||
|
|
@ -671,6 +709,9 @@
|
|||
<div class="mod-end"></div></div>
|
||||
</div>
|
||||
|
||||
<!-- prompts and other modals needed in electron -->
|
||||
<dialog id="dialog"></dialog>
|
||||
|
||||
<!-- click + drag mouse tracking -->
|
||||
<div id="tracker"></div>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ self.kiri.lang['en-us'] = {
|
|||
edit: "edit",
|
||||
enable: "enable",
|
||||
disable: "disable",
|
||||
donate: "donate",
|
||||
export: "export",
|
||||
files: "files",
|
||||
filter: "filter",
|
||||
|
|
@ -521,6 +522,8 @@ self.kiri.lang['en-us'] = {
|
|||
|
||||
// CNC OUTLINE
|
||||
co_menu: "outline",
|
||||
co_merg_s: "merge overlap",
|
||||
co_merg_l: ["merge overlapping lines to prevent overcutting into adjacent solids"],
|
||||
co_dogb_s: "dogbones",
|
||||
co_dogb_l: ["insert dogbone cuts","into inside corners"],
|
||||
co_dogr_s: "reverse bones",
|
||||
|
|
@ -607,6 +610,8 @@ self.kiri.lang['en-us'] = {
|
|||
ci_abso_l: "degree rotation is absolute",
|
||||
ci_face_s: "face",
|
||||
ci_face_l: "select face to rotate facing up",
|
||||
ci_line_s: "linear",
|
||||
ci_line_l: "make linear passes along X then rotate Y",
|
||||
|
||||
// CNC LASER On/Off Operations (Carvera)
|
||||
cl_powr_s: "power",
|
||||
|
|
@ -692,6 +697,10 @@ self.kiri.lang['en-us'] = {
|
|||
ou_lays_l: ["mark layers for stacking. the layer above will be output in a different color which the laser can mark with a lower power. turns on layer grouping."],
|
||||
ou_drkn_s: "drag knife",
|
||||
ou_drkn_l: ["enable drag knife","output in gcode","cut radii are added","to corners with","cut down passes"],
|
||||
ou_stak_s: "fixed",
|
||||
ou_stak_l: ["output is a 3D stack of 2D paths rather than packing layers flat in 2D"],
|
||||
ou_maxp_s: "max power",
|
||||
ou_maxp_l: ["max power value in gcode. power % from settings will scale from 0 to this value"],
|
||||
|
||||
// OUTPUT FDM
|
||||
ou_nozl_s: "nozzle temp",
|
||||
|
|
@ -726,8 +735,10 @@ self.kiri.lang['en-us'] = {
|
|||
ou_zanc_l: ["controls the position of the part","when stock Z exceeds part Z"],
|
||||
ou_ztof_s: "z offset",
|
||||
ou_ztof_l: ["offset z anchor","in workspace units"],
|
||||
ou_ztop_s: "z top",
|
||||
ou_ztop_l: ["offset from stock bottom","to set start of cutting depth","in workspace units","* drill/contour ignore this *"],
|
||||
ou_zbot_s: "z bottom",
|
||||
ou_zbot_l: ["offset from part bottom","to limit cutting depth","in workspace units"],
|
||||
ou_zbot_l: ["offset from stock bottom","to limit cutting depth","in workspace units"],
|
||||
ou_zclr_s: "z clearance",
|
||||
ou_zclr_l: ["safe travel offset","from top of part","in workspace units"],
|
||||
ou_ztru_s: "z thru",
|
||||
|
|
@ -815,6 +826,8 @@ self.kiri.lang['en-us'] = {
|
|||
ad_lbir_l: ["always print shells touching the belt before any other shells. this is no longer a recommended setting based on extensive testing."],
|
||||
ad_altr_s: "alternating",
|
||||
ad_altr_l: ["alternate shell winding order","clockwise / counter-clockwise","may reduce warping in thin areas","and help with belt edge adhesion"],
|
||||
ad_zint_s: "interleave z",
|
||||
ad_zint_l: ["interleave Z heights with odd number shells"],
|
||||
ad_lret_s: "layer retract",
|
||||
ad_lret_l: ["force filament retraction","between layers"],
|
||||
ad_agap_s: "avoid gaps",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "Kiri:Moto 4.0.31",
|
||||
"name": "Kiri:Moto 4.1.0",
|
||||
"short_name": "Kiri:Moto",
|
||||
"description": "Slicer for 3D printers, CNC mills, laser cutters and more",
|
||||
"start_url": "/kiri/",
|
||||
|
|
|
|||
Loading…
Reference in a new issue