update code style, use gapp.register() for cam driver

This commit is contained in:
Stewart Allen 2022-02-21 10:01:19 -05:00
commit 836d53aad5
10 changed files with 4881 additions and 4862 deletions

View file

@ -2,49 +2,53 @@
"use strict"; "use strict";
self.kiri.load(function() { // dep: kiri.api
// dep: kiri-mode.cam.driver
gapp.register("kiri-mode.cam.animate", [], (root, exports) => {
let KIRI = self.kiri, const { kiri } = root;
CAM = KIRI.driver.CAM, const { api, driver } = kiri;
API, WORLD, SPACE, const { CAM } = driver;
meshes = {},
unitScale = 1,
progress, toolPosX, toolPosY, toolPosZ,
speedValues = [ 25, 12, 6, 3 ],
speedNames = [ "1x", "2x", "4x", "8x" ],
speedMax = speedValues.length - 1,
speedIndex = 0,
speedLabel,
speed,
color = 0,
pauseButton,
playButton,
posOffset = { x:0, y:0, z:0 };
// ---( CLIENT FUNCTIONS )--- let meshes = {},
unitScale = 1,
progress, toolPosX, toolPosY, toolPosZ,
speedValues = [ 25, 12, 6, 3 ],
speedNames = [ "1x", "2x", "4x", "8x" ],
speedMax = speedValues.length - 1,
speedIndex = 0,
speedLabel,
speed,
color = 0,
pauseButton,
playButton,
posOffset = { x:0, y:0, z:0 };
if (KIRI.client) // ---( CLIENT FUNCTIONS )---
CAM.animate_clear = function(api) {
API = api; kiri.load(() => {
KIRI.client.animate_cleanup(); if (!kiri.client) {
SPACE = KIRI.space; return;
WORLD = SPACE.world; }
const { moto } = root;
const { space } = moto;
const { world } = space;
function animate_clear(api) {
kiri.client.animate_cleanup();
$('layer-animate').innerHTML = ''; $('layer-animate').innerHTML = '';
$('layer-toolpos').innerHTML = ''; $('layer-toolpos').innerHTML = '';
Object.keys(meshes).forEach(id => deleteMesh(id)); Object.keys(meshes).forEach(id => deleteMesh(id));
} }
if (KIRI.client) function animate(api, delay) {
CAM.animate = function(api, delay) { kiri.client.animate_setup(api.conf.get(), data => {
API = api;
SPACE = KIRI.space;
WORLD = SPACE.world;
KIRI.client.animate_setup(API.conf.get(), data => {
checkMeshCommands(data); checkMeshCommands(data);
if (!(data && data.mesh_add)) { if (!(data && data.mesh_add)) {
return; return;
} }
const UC = API.uc; const UC = api.uc;
const layer = $('layer-animate'); const layer = $('layer-animate');
layer.innerHTML = ''; layer.innerHTML = '';
UC.setGroup(layer); UC.setGroup(layer);
@ -70,9 +74,109 @@ self.kiri.load(function() {
toolPosZ = UC.newInput('z', {disabled: true, size: 7}); toolPosZ = UC.newInput('z', {disabled: true, size: 7});
playButton.style.display = ''; playButton.style.display = '';
pauseButton.style.display = 'none'; pauseButton.style.display = 'none';
API.event.emit('animate', 'CAM'); api.event.emit('animate', 'CAM');
}); });
}; }
gapp.overlay(kiri.client, {
animate(data, ondone) {
kiri.client.send("animate", data, ondone);
},
animate_setup(settings, ondone) {
color = settings.controller.dark ? 0x888888 : 0;
unitScale = settings.controller.units === 'in' ? 1/25.4 : 1;
kiri.client.send("animate_setup", {settings}, ondone);
},
animate_cleanup(data, ondone) {
kiri.client.send("animate_cleanup", data, ondone);
}
});
gapp.overlay(CAM, {
animate,
animate_clear
});
function meshAdd(id, ind, pos) {
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3));
geo.setIndex(new THREE.BufferAttribute(new Uint32Array(ind), 1));
const mat = new THREE.LineBasicMaterial({
transparent: true,
opacity: 0.75,
color
});
const mesh = new THREE.LineSegments(geo, mat);
world.add(mesh);
meshes[id] = mesh;
}
function meshUpdates(id, updates) {
const mesh = meshes[id];
if (!mesh) {
return; // animate cancelled
}
const mpos = mesh.geometry.attributes.position;
for (let i=0, il=updates.length; i<il; ) {
const pos = updates[i++];
const val = updates[i++];
mpos.array[pos] = val;
}
mpos.needsUpdate = true;
space.update();
}
function deleteMesh(id) {
world.remove(meshes[id]);
delete meshes[id];
}
function step(opts) {
const { steps } = opts;
updateSpeed();
kiri.client.animate({speed, steps: 1}, handleGridUpdate);
}
function play(opts) {
const { steps } = opts;
updateSpeed();
if (steps !== 1) {
playButton.style.display = 'none';
pauseButton.style.display = '';
}
kiri.client.animate({speed, steps: steps || Infinity}, handleGridUpdate);
}
function fast(opts) {
const { steps } = opts;
updateSpeed(1);
playButton.style.display = 'none';
pauseButton.style.display = '';
kiri.client.animate({speed, steps: steps || Infinity}, handleGridUpdate);
}
function pause() {
playButton.style.display = '';
pauseButton.style.display = 'none';
kiri.client.animate({speed: 0}, handleGridUpdate);
}
function skip() {
api.show.alert('fast fowarding without animation');
playButton.style.display = 'none';
pauseButton.style.display = '';
updateSpeed(Infinity);
kiri.client.animate({speed, steps: Infinity, toend: true}, handleGridUpdate);
}
function handleGridUpdate(data) {
checkMeshCommands(data);
if (data && data.progress) {
progress.value = (data.progress * 100).toFixed(1)
}
}
function updateSpeed(inc = 0) { function updateSpeed(inc = 0) {
if (inc === Infinity) { if (inc === Infinity) {
@ -85,9 +189,9 @@ self.kiri.load(function() {
} }
function replay() { function replay() {
CAM.animate_clear(API); CAM.animate_clear(api);
setTimeout(() => { setTimeout(() => {
CAM.animate(API, 50); CAM.animate(api, 50);
}, 250); }, 250);
} }
@ -112,7 +216,7 @@ self.kiri.load(function() {
mesh.position.x = pos.x; mesh.position.x = pos.x;
mesh.position.y = pos.y; mesh.position.y = pos.y;
mesh.position.z = pos.z; mesh.position.z = pos.z;
SPACE.update(); space.update();
if (id !== 0) { if (id !== 0) {
toolPosX.value = ((pos.x + posOffset.x) * unitScale).toFixed(2); toolPosX.value = ((pos.x + posOffset.x) * unitScale).toFixed(2);
toolPosY.value = ((pos.y + posOffset.y) * unitScale).toFixed(2); toolPosY.value = ((pos.y + posOffset.y) * unitScale).toFixed(2);
@ -125,109 +229,19 @@ self.kiri.load(function() {
} }
} }
function meshAdd(id, ind, pos) { });
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(pos, 3)); // ---( WORKER FUNCTIONS )---
geo.setIndex(new THREE.BufferAttribute(new Uint32Array(ind), 1));
const mat = new THREE.LineBasicMaterial({ kiri.load(() => {
transparent: true, if (!kiri.worker) {
opacity: 0.75, return;
color
});
const mesh = new THREE.LineSegments(geo, mat);
WORLD.add(mesh);
meshes[id] = mesh;
} }
function meshUpdates(id, updates) {
const mesh = meshes[id];
if (!mesh) {
return; // animate cancelled
}
const mpos = mesh.geometry.attributes.position;
for (let i=0, il=updates.length; i<il; ) {
const pos = updates[i++];
const val = updates[i++];
mpos.array[pos] = val;
}
mpos.needsUpdate = true;
SPACE.update();
}
function deleteMesh(id) {
WORLD.remove(meshes[id]);
delete meshes[id];
}
function step(opts) {
const { steps } = opts;
updateSpeed();
KIRI.client.animate({speed, steps: 1}, handleGridUpdate);
}
function play(opts) {
const { steps } = opts;
updateSpeed();
if (steps !== 1) {
playButton.style.display = 'none';
pauseButton.style.display = '';
}
KIRI.client.animate({speed, steps: steps || Infinity}, handleGridUpdate);
}
function fast(opts) {
const { steps } = opts;
updateSpeed(1);
playButton.style.display = 'none';
pauseButton.style.display = '';
KIRI.client.animate({speed, steps: steps || Infinity}, handleGridUpdate);
}
function pause() {
playButton.style.display = '';
pauseButton.style.display = 'none';
KIRI.client.animate({speed: 0}, handleGridUpdate);
}
function skip() {
API.show.alert('fast fowarding without animation');
playButton.style.display = 'none';
pauseButton.style.display = '';
updateSpeed(Infinity);
KIRI.client.animate({speed, steps: Infinity, toend: true}, handleGridUpdate);
}
function handleGridUpdate(data) {
checkMeshCommands(data);
if (data && data.progress) {
progress.value = (data.progress * 100).toFixed(1)
}
}
if (KIRI.client)
KIRI.client.animate_setup = function(settings, ondone) {
color = settings.controller.dark ? 0x888888 : 0;
unitScale = settings.controller.units === 'in' ? 1/25.4 : 1;
KIRI.client.send("animate_setup", {settings}, ondone);
};
if (KIRI.client)
KIRI.client.animate = function(data, ondone) {
KIRI.client.send("animate", data, ondone);
};
if (KIRI.client)
KIRI.client.animate_cleanup = function(data, ondone) {
KIRI.client.send("animate_cleanup", data, ondone);
};
// ---( WORKER FUNCTIONS )---
let stock, center, grid, gridX, gridY, rez; let stock, center, grid, gridX, gridY, rez;
let path, pathIndex, tool, tools, last, toolID = 1; let path, pathIndex, tool, tools, last, toolID = 1;
if (KIRI.worker) kiri.worker.animate_setup = function(data, send) {
KIRI.worker.animate_setup = function(data, send) {
const { settings } = data; const { settings } = data;
const { process } = settings; const { process } = settings;
const print = current.print; const print = current.print;
@ -267,6 +281,31 @@ self.kiri.load(function() {
send.done(); send.done();
}; };
kiri.worker.animate = function(data, send) {
if (data.speed >= 0) {
renderSpeed = data.speed;
}
if (data.steps > 0) {
stepsRemain = data.steps;
}
if (data.toend) {
skipMode = !skipMode;
} else {
skipMode = false;
}
checkStash(send);
if (animating) {
return send.done();
}
renderPath(send);
};
kiri.worker.animate_cleanup = function(data, send) {
if (animating) {
animateClear = true;
}
};
function createGrid(stepsX, stepsY, size, step) { function createGrid(stepsX, stepsY, size, step) {
const gridPoints = stepsX * stepsY; const gridPoints = stepsX * stepsY;
const pos = new Float32Array(gridPoints * 3); const pos = new Float32Array(gridPoints * 3);
@ -300,33 +339,6 @@ self.kiri.load(function() {
return { pos, ind }; return { pos, ind };
} }
if (KIRI.worker)
KIRI.worker.animate = function(data, send) {
if (data.speed >= 0) {
renderSpeed = data.speed;
}
if (data.steps > 0) {
stepsRemain = data.steps;
}
if (data.toend) {
skipMode = !skipMode;
} else {
skipMode = false;
}
checkStash(send);
if (animating) {
return send.done();
}
renderPath(send);
};
if (KIRI.worker)
KIRI.worker.animate_cleanup = function(data, send) {
if (animating) {
animateClear = true;
}
};
let animateClear = false; let animateClear = false;
let animating = false; let animating = false;
let renderSpeed = 25; let renderSpeed = 25;
@ -510,7 +522,7 @@ self.kiri.load(function() {
} }
// load renderer code in worker context only // load renderer code in worker context only
if (KIRI.worker && false) if (kiri.worker && false)
fetch('/wasm/kiri-ani.wasm') fetch('/wasm/kiri-ani.wasm')
.then(response => response.arrayBuffer()) .then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, { .then(bytes => WebAssembly.instantiate(bytes, {
@ -535,3 +547,5 @@ self.kiri.load(function() {
}); });
}); });
});

File diff suppressed because it is too large Load diff

View file

@ -2,54 +2,55 @@
"use strict"; "use strict";
(function() { // dep: main.kiri
// use: kiri.api
// use: kiri.widgets
gapp.register("kiri-mode.cam.driver", [], (root, exports) => {
const KIRI = self.kiri, const { kiri } = root;
CAM = KIRI.driver.CAM = { const { driver } = kiri;
// init, // src/mode/cam/client.js
// slice, // src/mode/cam/slice.js
// prepare, // src/mode/cam/prepare.js
// export // src/mode/cam/export.js
},
CPRO = CAM.process = {
LEVEL: 1,
ROUGH: 2,
OUTLINE: 3,
CONTOUR_X: 4,
CONTOUR_Y: 5,
TRACE: 6,
DRILL: 7
};
// defer loading until KIRI.client and KIRI.worker exist const CAM = driver.CAM = {};
KIRI.load(function(API) {
if (KIRI.client) CAM.process = {
CAM.traces = function(ondone, single) { LEVEL: 1,
KIRI.client.sync(); ROUGH: 2,
let settings = API.conf.get(); OUTLINE: 3,
let widgets = API.widgets.map(); CONTOUR_X: 4,
KIRI.client.send("cam_traces", { settings, single }, output => { CONTOUR_Y: 5,
let ids = []; TRACE: 6,
KIRI.codec.decode(output).forEach(rec => { DRILL: 7
ids.push(rec.id); };
widgets[rec.id].traces = rec.traces;
});
ondone(ids);
});
};
if (KIRI.worker) // defer loading until kiri.client and kiri.worker exist
KIRI.worker.cam_traces = function(data, send) { kiri.load(api => {
const { settings, single } = data;
const widgets = Object.values(wcache);
const fresh = widgets.filter(widget => CAM.traces(settings, widget, single));
send.done(KIRI.codec.encode(fresh.map(widget => { return {
id: widget.id,
traces: widget.traces,
} } )));
};
if (kiri.client)
CAM.traces = function(ondone, single) {
kiri.client.sync();
const settings = api.conf.get();
const widgets = api.widgets.map();
kiri.client.send("cam_traces", { settings, single }, output => {
const ids = [];
kiri.codec.decode(output).forEach(rec => {
ids.push(rec.id);
widgets[rec.id].traces = rec.traces;
});
ondone(ids);
}); });
};
})(); if (kiri.worker)
kiri.worker.cam_traces = function(data, send) {
const { settings, single } = data;
const widgets = Object.values(wcache);
const fresh = widgets.filter(widget => CAM.traces(settings, widget, single));
send.done(kiri.codec.encode(fresh.map(widget => { return {
id: widget.id,
traces: widget.traces,
} } )));
};
});
});

View file

@ -2,333 +2,335 @@
"use strict"; "use strict";
(function() { // dep: geo.base
// dep: geo.polygons
gapp.register("kiri-mode.cam.export", [], (root, exports) => {
let KIRI = self.kiri, const { base, kiri } = root;
BASE = self.base, const { polygons, util } = base;
POLY = BASE.polygons, const { driver } = kiri;
UTIL = BASE.util, const { CAM } = driver;
CAM = KIRI.driver.CAM,
PRO = CAM.process;
/** const POLY = polygons;
* @returns {Array} gcode lines const PRO = CAM.process;
*/
CAM.export = function(print, online) {
let widget = print.widgets[0];
if (!widget) return;
let i, /**
time = 0, * @returns {Array} gcode lines
lines = 0, */
bytes = 0, CAM.export = function(print, online) {
opnum = 0, const widget = print.widgets[0];
factor = 1, if (!widget) return;
output = [],
spindle = 0,
settings = print.settings,
device = settings.device,
gcodes = settings.device || {},
origin = settings.origin || { x: 0, y: 0, z: 0 },
tools = settings.tools,
space = gcodes.gcodeSpace ? ' ' : '',
isRML = device.gcodeFExt.toLowerCase() === 'rml',
stripComments = gcodes.gcodeStrip || false,
cmdToolChange = gcodes.gcodeChange || [ "M6 T{tool}" ],
cmdSpindle = gcodes.gcodeSpindle || [ "M3 S{speed}" ],
cmdDwell = gcodes.gcodeDwell || [ "G4 P{time}" ],
dev = settings.device,
spro = settings.process,
maxZd = spro.camFastFeedZ,
maxXYd = spro.camFastFeed,
decimals = BASE.config.gcode_decimals || 4,
pos = { x:null, y:null, z:null, f:null, t:null },
line,
cidx,
mode,
point,
points = 0,
stock = settings.stock || { },
hasStock = spro.camStockOn && stock.x && stock.y && stock.z,
ztOff = hasStock ? stock.z - widget.track.top : 0,
bounds = widget.getBoundingBox(),
zmax = hasStock ? stock.z : bounds.max.z,
runbox = {
max: { x:-Infinity, y:-Infinity, z:-Infinity},
min: { x:Infinity, y:Infinity, z:Infinity}
},
offset = {
x: -origin.x,
y: origin.y
},
consts = {
tool: 0,
tool_name: "unknown",
top: (offset ? dev.bedDepth : dev.bedDepth/2),
left: (offset ? 0 : -dev.bedWidth/2),
right: (offset ? dev.bedWidth : dev.bedWidth/2),
bottom: (offset ? 0 : -dev.bedDepth/2),
time_sec: 0,
time_ms: 0,
time: 0
}
function section(section) { const { settings } = print;
append(); const { device, tools } = settings;
online({ section });
let i,
time = 0,
lines = 0,
bytes = 0,
opnum = 0,
factor = 1,
output = [],
spindle = 0,
origin = settings.origin || { x: 0, y: 0, z: 0 },
space = device.gcodeSpace ? ' ' : '',
isRML = device.gcodeFExt.toLowerCase() === 'rml',
stripComments = device.gcodeStrip || false,
cmdToolChange = device.gcodeChange || [ "M6 T{tool}" ],
cmdSpindle = device.gcodeSpindle || [ "M3 S{speed}" ],
cmdDwell = device.gcodeDwell || [ "G4 P{time}" ],
dev = settings.device,
spro = settings.process,
maxZd = spro.camFastFeedZ,
maxXYd = spro.camFastFeed,
decimals = base.config.gcode_decimals || 4,
pos = { x:null, y:null, z:null, f:null, t:null },
line,
cidx,
mode,
point,
points = 0,
stock = settings.stock || { },
hasStock = spro.camStockOn && stock.x && stock.y && stock.z,
ztOff = hasStock ? stock.z - widget.track.top : 0,
bounds = widget.getBoundingBox(),
zmax = hasStock ? stock.z : bounds.max.z,
runbox = {
max: { x:-Infinity, y:-Infinity, z:-Infinity},
min: { x:Infinity, y:Infinity, z:Infinity}
},
offset = {
x: -origin.x,
y: origin.y
},
consts = {
tool: 0,
tool_name: "unknown",
top: (offset ? dev.bedDepth : dev.bedDepth/2),
left: (offset ? 0 : -dev.bedWidth/2),
right: (offset ? dev.bedWidth : dev.bedWidth/2),
bottom: (offset ? 0 : -dev.bedDepth/2),
time_sec: 0,
time_ms: 0,
time: 0
} }
function append(line) { function section(section) {
if (line) {
lines++;
bytes += line.length;
output.append(line);
}
if (!line || output.length > 1000) {
online(output.join("\r\n"));
output = [];
}
}
function filterEmit(array, consts) {
if (!array) return;
for (i=0; i<array.length; i++) {
line = print.constReplace(array[i], consts);
if (!isRML && stripComments && (cidx = line.indexOf(";")) >= 0) {
line = line.substring(0, cidx).trim();
if (line.length === 0) continue;
}
if (line.indexOf('G20') === 0) {
factor = 1/25.4;
consts.top = (offset ? dev.bedDepth : dev.bedDepth/2) * factor;
consts.left = (offset ? 0 : -dev.bedWidth/2) * factor;
consts.right = (offset ? dev.bedWidth : dev.bedWidth/2) * factor;
consts.bottom = (offset ? 0 : -dev.bedDepth/2) * factor;
} else if (line.indexOf('G21') === 0) {
factor = 1;
}
append(line);
}
}
function add0(val, opt) {
let s = val.toString(),
d = s.indexOf(".");
if (d < 0) {
return opt ? s : s + '.0';
} else {
return val.toFixed(decimals);
}
}
function toolByNumber(number) {
for (let i=0; i<tools.length; i++) {
if (tools[i].number === number) return tools[i];
}
return undefined;
}
function toolNameByNumber(number) {
for (let i=0; i<tools.length; i++) {
if (tools[i].number === number) return tools[i].name;
}
return "unknown";
}
function moveTo(out) {
let newpos = out.point;
// no point == dwell
// out.speed = time to dwell in ms
if (!newpos) {
time += out.speed / 60;
consts.time_sec = out.speed / 1000;
consts.time_ms = out.speed;
consts.time = consts.time_sec;
filterEmit(cmdDwell, consts);
return;
}
newpos.x = UTIL.round(newpos.x, decimals);
newpos.y = UTIL.round(newpos.y, decimals);
newpos.z = UTIL.round(newpos.z, decimals);
// on tool change
if (out.tool != pos.t) {
pos.t = out.tool;
consts.tool = pos.t;
consts.tool_name = toolNameByNumber(out.tool);
filterEmit(cmdToolChange, consts);
}
// first point out sets the current position (but not Z)
// hacky AF way to split initial x,y,z into z then x,y
if (points === 0) {
pos.x = pos.y = pos.z = 0;
points++;
moveTo({
tool: out.tool,
point: { x: 0, y: 0, z: newpos.z }
});
moveTo({
tool: out.tool,
point: { x: newpos.x, y: newpos.y, z: newpos.z }
});
points--;
return;
}
let speed = out.speed,
nl = [speed ? 'G1' : 'G0'],
dx = newpos.x - pos.x,
dy = newpos.y - pos.y,
dz = newpos.z - pos.z,
maxf = dz ? maxZd : maxXYd,
feed = Math.min(speed || maxf, maxf),
dist = Math.sqrt(dx * dx + dy * dy + dz * dz),
newFeed = feed && feed !== pos.f;
// drop dup points (all deltas are 0)
if (!(dx || dy || dz)) {
return;
}
if (newpos.x !== pos.x) {
pos.x = newpos.x;
runbox.min.x = Math.min(runbox.min.x, pos.x);
runbox.max.x = Math.max(runbox.max.x, pos.x);
nl.append(space).append("X").append(add0(pos.x * factor));
}
if (newpos.y !== pos.y) {
pos.y = newpos.y;
runbox.min.y = Math.min(runbox.min.y, pos.y);
runbox.max.y = Math.max(runbox.max.y, pos.y);
nl.append(space).append("Y").append(add0(pos.y * factor));
}
if (newpos.z !== pos.z) {
pos.z = newpos.z;
runbox.min.z = Math.min(runbox.min.z, pos.z);
runbox.max.z = Math.max(runbox.max.z, pos.z);
nl.append(space).append("Z").append(add0(pos.z * factor));
}
if (newFeed) {
pos.f = feed;
nl.append(space).append("F").append(add0(feed * factor, true));
}
// temp hack to support RML1 dialect from a file extensions trigger
if (isRML) {
if (speed) {
if (newFeed) {
append(`VS${feed};`);
}
nl = [ "Z", add0(pos.x * factor), ",", add0(pos.y * factor), ",", add0(pos.z * factor), ";" ];
} else {
nl = [ "PU", add0(pos.x * factor), ",", add0(pos.y * factor), ";" ];
}
}
// update time calculation
time += (dist / (pos.f || 1000)) * 60;
// if (comment && !stripComments) {
// nl.append(" ; ").append(comment);
// nl.append(" ; ").append(points);
// }
append(nl.join(''));
points++;
}
section('header');
if (!stripComments) {
append(`; Generated by Kiri:Moto ${KIRI.version}`);
append(`; ${new Date().toString()}`);
filterEmit(["; Bed left:{left} right:{right} top:{top} bottom:{bottom}"], consts);
if (hasStock) {
append(`; Stock X:${stock.x.round(2)} Y:${stock.y.round(2)} Z:${stock.z.round(2)}`);
}
append(`; Target: ${settings.filter[settings.mode]}`);
append("; --- process ---");
for (let pk in spro) {
if (pk !== "ops" && pk !== "op2") {
append("; " + pk + " = " + spro[pk]);
}
}
}
// collect tool info to add to header
let toolz = {}, ctool;
// remap points as necessary for origins, offsets, inversions
print.output.forEach(function(layer) {
layer.forEach(function(out) {
if (out.tool && out.tool !== ctool) {
ctool = toolByNumber(out.tool);
toolz[out.tool] = ctool;
}
point = out.point;
if (!point || point.mod) return;
// ensure not point is modified twice
point.mod = 1;
if (offset) {
point.x += offset.x;
point.y += offset.y;
}
if (spro.outputInvertX) point.x = -point.x;
if (spro.outputInvertY) point.y = -point.y;
if (spro.camOriginTop) point.z = point.z - zmax;
});
});
if (!stripComments) {
// emit tools used in comments
append("; --- tools ---");
Object.keys(toolz).sort().forEach(tn => {
let tool = toolz[tn];
append(`; tool=${tn} flute=${tool.flute_diam} len=${tool.flute_len} metric=${tool.metric}`);
});
}
// emit gcode preamble
filterEmit(gcodes.gcodePre, consts);
// emit all points in layer/point order
print.output.forEach(function (layerout) {
if (mode !== layerout.mode) {
if (mode && !stripComments) append("; ending " + mode + " op after " + Math.round(time/60) + " seconds");
mode = layerout.mode;
if (mode) section(`op-${opnum++}-${mode}`);
if (!stripComments) append("; starting " + mode + " op");
}
if (layerout.spindle && layerout.spindle !== spindle) {
spindle = layerout.spindle;
if (spindle > 0) {
let speed = Math.abs(spindle);
filterEmit(cmdSpindle, {
speed, spindle: speed, rpm: speed
});
} else {
append("M4");
}
// append((spindle > 0 ? "M3" : "M4") + " S" + Math.abs(spindle));
}
layerout.forEach(function(out) {
moveTo(out);
});
});
if (mode && !stripComments) append("; ending " + mode + " op after " + Math.round(time/60) + " seconds");
// emit gcode post
section('footer');
filterEmit(gcodes.gcodePost, consts);
// flush buffered gcode
append(); append();
online({ section });
}
print.time = time; function append(line) {
print.lines = lines; if (line) {
print.bytes = bytes + lines - 1; lines++;
print.bounds = runbox; bytes += line.length;
}; output.append(line);
}
if (!line || output.length > 1000) {
online(output.join("\r\n"));
output = [];
}
}
})(); function filterEmit(array, consts) {
if (!array) return;
for (i=0; i<array.length; i++) {
line = print.constReplace(array[i], consts);
if (!isRML && stripComments && (cidx = line.indexOf(";")) >= 0) {
line = line.substring(0, cidx).trim();
if (line.length === 0) continue;
}
if (line.indexOf('G20') === 0) {
factor = 1/25.4;
consts.top = (offset ? dev.bedDepth : dev.bedDepth/2) * factor;
consts.left = (offset ? 0 : -dev.bedWidth/2) * factor;
consts.right = (offset ? dev.bedWidth : dev.bedWidth/2) * factor;
consts.bottom = (offset ? 0 : -dev.bedDepth/2) * factor;
} else if (line.indexOf('G21') === 0) {
factor = 1;
}
append(line);
}
}
function add0(val, opt) {
let s = val.toString(),
d = s.indexOf(".");
if (d < 0) {
return opt ? s : s + '.0';
} else {
return val.toFixed(decimals);
}
}
function toolByNumber(number) {
for (let i=0; i<tools.length; i++) {
if (tools[i].number === number) return tools[i];
}
return undefined;
}
function toolNameByNumber(number) {
for (let i=0; i<tools.length; i++) {
if (tools[i].number === number) return tools[i].name;
}
return "unknown";
}
function moveTo(out) {
let newpos = out.point;
// no point == dwell
// out.speed = time to dwell in ms
if (!newpos) {
time += out.speed / 60;
consts.time_sec = out.speed / 1000;
consts.time_ms = out.speed;
consts.time = consts.time_sec;
filterEmit(cmdDwell, consts);
return;
}
newpos.x = util.round(newpos.x, decimals);
newpos.y = util.round(newpos.y, decimals);
newpos.z = util.round(newpos.z, decimals);
// on tool change
if (out.tool != pos.t) {
pos.t = out.tool;
consts.tool = pos.t;
consts.tool_name = toolNameByNumber(out.tool);
filterEmit(cmdToolChange, consts);
}
// first point out sets the current position (but not Z)
// hacky AF way to split initial x,y,z into z then x,y
if (points === 0) {
pos.x = pos.y = pos.z = 0;
points++;
moveTo({
tool: out.tool,
point: { x: 0, y: 0, z: newpos.z }
});
moveTo({
tool: out.tool,
point: { x: newpos.x, y: newpos.y, z: newpos.z }
});
points--;
return;
}
let speed = out.speed,
nl = [speed ? 'G1' : 'G0'],
dx = newpos.x - pos.x,
dy = newpos.y - pos.y,
dz = newpos.z - pos.z,
maxf = dz ? maxZd : maxXYd,
feed = Math.min(speed || maxf, maxf),
dist = Math.sqrt(dx * dx + dy * dy + dz * dz),
newFeed = feed && feed !== pos.f;
// drop dup points (all deltas are 0)
if (!(dx || dy || dz)) {
return;
}
if (newpos.x !== pos.x) {
pos.x = newpos.x;
runbox.min.x = Math.min(runbox.min.x, pos.x);
runbox.max.x = Math.max(runbox.max.x, pos.x);
nl.append(space).append("X").append(add0(pos.x * factor));
}
if (newpos.y !== pos.y) {
pos.y = newpos.y;
runbox.min.y = Math.min(runbox.min.y, pos.y);
runbox.max.y = Math.max(runbox.max.y, pos.y);
nl.append(space).append("Y").append(add0(pos.y * factor));
}
if (newpos.z !== pos.z) {
pos.z = newpos.z;
runbox.min.z = Math.min(runbox.min.z, pos.z);
runbox.max.z = Math.max(runbox.max.z, pos.z);
nl.append(space).append("Z").append(add0(pos.z * factor));
}
if (newFeed) {
pos.f = feed;
nl.append(space).append("F").append(add0(feed * factor, true));
}
// temp hack to support RML1 dialect from a file extensions trigger
if (isRML) {
if (speed) {
if (newFeed) {
append(`VS${feed};`);
}
nl = [ "Z", add0(pos.x * factor), ",", add0(pos.y * factor), ",", add0(pos.z * factor), ";" ];
} else {
nl = [ "PU", add0(pos.x * factor), ",", add0(pos.y * factor), ";" ];
}
}
// update time calculation
time += (dist / (pos.f || 1000)) * 60;
// if (comment && !stripComments) {
// nl.append(" ; ").append(comment);
// nl.append(" ; ").append(points);
// }
append(nl.join(''));
points++;
}
section('header');
if (!stripComments) {
append(`; Generated by Kiri:Moto ${kiri.version}`);
append(`; ${new Date().toString()}`);
filterEmit(["; Bed left:{left} right:{right} top:{top} bottom:{bottom}"], consts);
if (hasStock) {
append(`; Stock X:${stock.x.round(2)} Y:${stock.y.round(2)} Z:${stock.z.round(2)}`);
}
append(`; Target: ${settings.filter[settings.mode]}`);
append("; --- process ---");
for (let pk in spro) {
if (pk !== "ops" && pk !== "op2") {
append("; " + pk + " = " + spro[pk]);
}
}
}
// collect tool info to add to header
let toolz = {}, ctool;
// remap points as necessary for origins, offsets, inversions
print.output.forEach(function(layer) {
layer.forEach(function(out) {
if (out.tool && out.tool !== ctool) {
ctool = toolByNumber(out.tool);
toolz[out.tool] = ctool;
}
point = out.point;
if (!point || point.mod) return;
// ensure not point is modified twice
point.mod = 1;
if (offset) {
point.x += offset.x;
point.y += offset.y;
}
if (spro.outputInvertX) point.x = -point.x;
if (spro.outputInvertY) point.y = -point.y;
if (spro.camOriginTop) point.z = point.z - zmax;
});
});
if (!stripComments) {
// emit tools used in comments
append("; --- tools ---");
Object.keys(toolz).sort().forEach(tn => {
let tool = toolz[tn];
append(`; tool=${tn} flute=${tool.flute_diam} len=${tool.flute_len} metric=${tool.metric}`);
});
}
// emit gcode preamble
filterEmit(device.gcodePre, consts);
// emit all points in layer/point order
print.output.forEach(function (layerout) {
if (mode !== layerout.mode) {
if (mode && !stripComments) append("; ending " + mode + " op after " + Math.round(time/60) + " seconds");
mode = layerout.mode;
if (mode) section(`op-${opnum++}-${mode}`);
if (!stripComments) append("; starting " + mode + " op");
}
if (layerout.spindle && layerout.spindle !== spindle) {
spindle = layerout.spindle;
if (spindle > 0) {
let speed = Math.abs(spindle);
filterEmit(cmdSpindle, {
speed, spindle: speed, rpm: speed
});
} else {
append("M4");
}
// append((spindle > 0 ? "M3" : "M4") + " S" + Math.abs(spindle));
}
layerout.forEach(function(out) {
moveTo(out);
});
});
if (mode && !stripComments) append("; ending " + mode + " op after " + Math.round(time/60) + " seconds");
// emit gcode post
section('footer');
filterEmit(device.gcodePost, consts);
// flush buffered gcode
append();
print.time = time;
print.lines = lines;
print.bytes = bytes + lines - 1;
print.bounds = runbox;
};
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,354 +2,355 @@
"use strict"; "use strict";
(function() { // dep: geo.point
// dep: geo.polygons
// dep: kiri.slice
gapp.register("kiri-mode.cam.slice", [], (root, exports) => {
let KIRI = self.kiri, const { base, kiri } = root;
BASE = self.base, const { driver, newSlice } = kiri;
UTIL = BASE.util, const { polygons, newPoint, newPolygon } = base;
CAM = KIRI.driver.CAM, const { CAM } = driver;
OPS = CAM.OPS, const { OPS } = CAM;
PRO = CAM.process,
POLY = BASE.polygons,
newSlice = KIRI.newSlice,
newPoint = BASE.newPoint,
newPolygon = BASE.newPolygon;
/** const PRO = CAM.process;
* DRIVER SLICE CONTRACT const POLY = polygons;
*
* @param {Object} settings
* @param {Widget} widget
* @param {Function} output
*/
CAM.slice = function(settings, widget, onupdate, ondone) {
let mesh = widget.mesh,
proc = settings.process,
stock = settings.stock || {},
hasStock = stock.x && stock.y && stock.z && proc.camStockOn,
camOps = widget.camops = [],
sliceAll = widget.slices = [],
bounds = widget.getBoundingBox(),
zBottom = proc.camZBottom,
zMin = Math.max(bounds.min.z, zBottom),
zMax = bounds.max.z,
zThru = zBottom === 0 ? (proc.camZThru || 0) : 0,
wztop = widget.track.top,
ztOff = hasStock ? stock.z - wztop : 0,
zTop = zMax + ztOff,
minToolDiam = Infinity,
maxToolDiam = -Infinity,
thruHoles,
dark = settings.controller.dark ? true : false,
color = dark ? 0xbbbbbb : 0,
tabs = widget.anno.tab,
unsafe = proc.camExpertFast,
units = settings.controller.units === 'in' ? 25.4 : 1;
if (tabs) { /**
// make tab polygons * DRIVER SLICE CONTRACT
*
* @param {Object} settings
* @param {Widget} widget
* @param {Function} output
*/
CAM.slice = function(settings, widget, onupdate, ondone) {
let mesh = widget.mesh,
proc = settings.process,
stock = settings.stock || {},
hasStock = stock.x && stock.y && stock.z && proc.camStockOn,
camOps = widget.camops = [],
sliceAll = widget.slices = [],
bounds = widget.getBoundingBox(),
zBottom = proc.camZBottom,
zMin = Math.max(bounds.min.z, zBottom),
zMax = bounds.max.z,
zThru = zBottom === 0 ? (proc.camZThru || 0) : 0,
wztop = widget.track.top,
ztOff = hasStock ? stock.z - wztop : 0,
zTop = zMax + ztOff,
minToolDiam = Infinity,
maxToolDiam = -Infinity,
thruHoles,
dark = settings.controller.dark ? true : false,
color = dark ? 0xbbbbbb : 0,
tabs = widget.anno.tab,
unsafe = proc.camExpertFast,
units = settings.controller.units === 'in' ? 25.4 : 1;
if (tabs) {
// make tab polygons
tabs.forEach(tab => {
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 m4 = new THREE.Matrix4().makeRotationFromQuaternion(
new THREE.Quaternion(tab.rot._x, tab.rot._y, tab.rot._z, tab.rot._w)
);
poly.points = poly.points
.map(p => new THREE.Vector3(p.x,p.y,p.z).applyMatrix4(m4))
.map(v => newPoint(v.x, v.y, v.z));
poly.move(point);
tab.poly = poly;
// tslice.output().setLayer("tabs", 0xff0000).addPoly(poly);
// sliceAll.push(tslice);
});
}
if (unsafe) {
console.log("disabling overhang safeties");
}
if (!proc.ops || proc.ops.length === 0) {
throw 'no processes specified';
}
if (stock.x && stock.y && stock.z) {
if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) {
throw 'stock X too small for part. disable stock or use offset stock';
}
if (stock.y + 0.00001 < bounds.max.y - bounds.min.y) {
throw 'stock Y too small for part. disable stock or use offset stock';
}
if (stock.z + 0.00001 < bounds.max.z - bounds.min.z) {
throw 'stock Z too small for part. disable stock or use offset stock';
}
}
if (zMin >= bounds.max.z) {
throw `invalid z bottom ${(zMin/units).round(3)} >= bounds z max ${(zMax/units).round(3)}`;
}
let mark = Date.now();
let slicer = new kiri.cam_slicer(widget.getPoints(), {
zlist: true,
zline: true
});
function updateToolDiams(toolDiam) {
minToolDiam = Math.min(minToolDiam, toolDiam);
maxToolDiam = Math.max(maxToolDiam, toolDiam);
}
let state = {
settings,
widget,
bounds,
tabs,
cutTabs,
cutPolys,
healPolys,
slicer,
sliceAll,
updateToolDiams,
zBottom,
zThru,
ztOff,
zMax,
zTop,
unsafe,
color,
dark
};
let opList = [
// silently preface op list with OpShadow
new CAM.OPS.shadow(state, { type: "shadow" })
];
if (false) {
opList.push(new CAM.OPS.xray(state, { type: "xray" }));
}
let opSum = 0;
let opTot = opList.map(op => op.weight()).reduce((a,v) => a + v);
for (let op of proc.ops.filter(op => !op.disabled)) {
let opfn = OPS[op.type];
if (opfn) {
let opin = new opfn(state, op);
opList.push(opin);
opTot += opin.weight();
}
}
// give ops access to entire sequence
state.ops = opList;
for (let op of opList) {
let weight = op.weight();
op.slice((progress, message) => {
onupdate((opSum + (progress * weight)) / opTot, message || op.type());
});
camOps.push(op);
opSum += weight;
}
// reindex
sliceAll.forEach((slice, index) => slice.index = index);
// used in printSetup()
// used in CAM.prepare.getZClearPath()
// add tabs to terrain tops so moves avoid them
if (tabs) {
state.terrain.forEach(slab => {
tabs.forEach(tab => { tabs.forEach(tab => {
let zero = newPoint(0,0,0); if (tab.pos.z + tab.dim.z/2 >= slab.z) {
let point = newPoint(tab.pos.x, tab.pos.y, tab.pos.z); let all = [...slab.tops, tab.poly];
let poly = newPolygon().centerRectangle(zero, tab.dim.x, tab.dim.y); slab.tops = POLY.union(all, 0, true);
let tslice = newSlice(0); // slab.slice.output()
let m4 = new THREE.Matrix4().makeRotationFromQuaternion( // .setLayer("debug-tabs", {line: 0x880088, thin: true })
new THREE.Quaternion(tab.rot._x, tab.rot._y, tab.rot._z, tab.rot._w) // .addPolys(POLY.setZ(slab.tops.clone(true), slab.z), { thin: true });
);
poly.points = poly.points
.map(p => new THREE.Vector3(p.x,p.y,p.z).applyMatrix4(m4))
.map(v => newPoint(v.x, v.y, v.z));
poly.move(point);
tab.poly = poly;
// tslice.output().setLayer("tabs", 0xff0000).addPoly(poly);
// sliceAll.push(tslice);
});
}
if (unsafe) {
console.log("disabling overhang safeties");
}
if (!proc.ops || proc.ops.length === 0) {
throw 'no processes specified';
}
if (stock.x && stock.y && stock.z) {
if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) {
throw 'stock X too small for part. disable stock or use offset stock';
}
if (stock.y + 0.00001 < bounds.max.y - bounds.min.y) {
throw 'stock Y too small for part. disable stock or use offset stock';
}
if (stock.z + 0.00001 < bounds.max.z - bounds.min.z) {
throw 'stock Z too small for part. disable stock or use offset stock';
}
}
if (zMin >= bounds.max.z) {
throw `invalid z bottom ${(zMin/units).round(3)} >= bounds z max ${(zMax/units).round(3)}`;
}
let mark = Date.now();
let slicer = new KIRI.cam_slicer(widget.getPoints(), {
zlist: true,
zline: true
});
function updateToolDiams(toolDiam) {
minToolDiam = Math.min(minToolDiam, toolDiam);
maxToolDiam = Math.max(maxToolDiam, toolDiam);
}
let state = {
settings,
widget,
bounds,
tabs,
cutTabs,
cutPolys,
healPolys,
slicer,
sliceAll,
updateToolDiams,
zBottom,
zThru,
ztOff,
zMax,
zTop,
unsafe,
color,
dark
};
let opList = [
// silently preface op list with OpShadow
new CAM.OPS.shadow(state, { type: "shadow" })
];
if (false) {
opList.push(new CAM.OPS.xray(state, { type: "xray" }));
}
let opSum = 0;
let opTot = opList.map(op => op.weight()).reduce((a,v) => a + v);
for (let op of proc.ops.filter(op => !op.disabled)) {
let opfn = OPS[op.type];
if (opfn) {
let opin = new opfn(state, op);
opList.push(opin);
opTot += opin.weight();
}
}
// give ops access to entire sequence
state.ops = opList;
for (let op of opList) {
let weight = op.weight();
op.slice((progress, message) => {
onupdate((opSum + (progress * weight)) / opTot, message || op.type());
});
camOps.push(op);
opSum += weight;
}
// reindex
sliceAll.forEach((slice, index) => slice.index = index);
// used in printSetup()
// used in CAM.prepare.getZClearPath()
// add tabs to terrain tops so moves avoid them
if (tabs) {
state.terrain.forEach(slab => {
tabs.forEach(tab => {
if (tab.pos.z + tab.dim.z/2 >= slab.z) {
let all = [...slab.tops, tab.poly];
slab.tops = POLY.union(all, 0, true);
// slab.slice.output()
// .setLayer("debug-tabs", {line: 0x880088, thin: true })
// .addPolys(POLY.setZ(slab.tops.clone(true), slab.z), { thin: true });
}
});
});
}
// 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 shadowOff = maxToolDiam < 0 ? allpoly :
POLY.offset(allpoly, [minToolDiam/2,maxToolDiam/2], { count: 2, flat: true });
state.terrain.forEach(level => level.tops.appendAll(shadowOff));
widget.terrain = state.skipTerrain ? null : state.terrain;
widget.minToolDiam = minToolDiam;
widget.maxToolDiam = maxToolDiam;
ondone();
};
CAM.addDogbones = function(poly, dist, reverse) {
if (Array.isArray(poly)) {
return poly.forEach(p => CAM.addDogbones(p, dist));
}
if (poly.open) return;
let isCW = poly.isClockwise();
if (reverse || poly.parent) isCW = !isCW;
let oldpts = poly.points.slice();
let lastpt = oldpts[oldpts.length - 1];
let lastsl = lastpt.slopeTo(oldpts[0]).toUnit();
let newpts = [ ];
for (let i=0; i<oldpts.length + 1; i++) {
let nextpt = oldpts[i % oldpts.length];
let nextsl = lastpt.slopeTo(nextpt).toUnit();
let adiff = lastsl.angleDiff(nextsl, true);
let bdiff = ((adiff < 0 ? (180 - adiff) : (180 + adiff)) / 2) + 180;
if (isCW && adiff > 45) {
let newa = BASE.newSlopeFromAngle(lastsl.angle + bdiff);
newpts.push(lastpt.projectOnSlope(newa, dist));
newpts.push(lastpt.clone());
} else if (!isCW && adiff < -45) {
let newa = BASE.newSlopeFromAngle(lastsl.angle - bdiff);
newpts.push(lastpt.projectOnSlope(newa, dist));
newpts.push(lastpt.clone());
}
lastsl = nextsl;
lastpt = nextpt;
if (i < oldpts.length) {
newpts.push(nextpt);
}
}
poly.points = newpts;
if (poly.inner) {
CAM.addDogbones(poly.inner, dist, true);
}
};
CAM.traces = function(settings, widget, single) {
if (widget.traces && widget.trace_single === single) {
// do no work if cached
return false;
}
let slicer = new KIRI.cam_slicer(widget.getPoints(), {
zlist: true,
zline: true
});
let indices = [...new Set(Object.keys(slicer.zFlat)
.map(kv => parseFloat(kv).round(5))
.appendAll(Object.entries(slicer.zLine).map(ze => {
let [ zk, zv ] = ze;
return zv > 1 ? parseFloat(zk).round(5) : null;
})
.filter(v => v !== null)))]
.sort((a,b) => b - a);
let traces = [];
// find and trim polys (including open) to shadow
let oneach = (data, index, total) => {
if (single) {
for (let line of data.lines) {
if (line.p1.distTo2D(line.p2) > 1) {
traces.push(newPolygon().append(line.p1).append(line.p2).setOpen());
}
} }
} else });
BASE.polygons.flatten(data.tops,null,true).forEach(poly => { });
poly.inner = null; }
poly.parent = null;
let z = poly.getZ(); // add shadow perimeter to terrain to catch outside moves off part
for (let i=0, il=traces.length; i<il; i++) { let tabpoly = tabs ? tabs.map(tab => tab.poly) : [];
let trace = traces[i]; let allpoly = POLY.union([...state.shadowTop.tops, ...tabpoly, ...state.shadowTop.slice.shadow], 0, true);
// only compare polys farther apart in Z let shadowOff = maxToolDiam < 0 ? allpoly :
if (Math.abs(z - trace.getZ()) > 0.01) { POLY.offset(allpoly, [minToolDiam/2,maxToolDiam/2], { count: 2, flat: true });
state.terrain.forEach(level => level.tops.appendAll(shadowOff));
widget.terrain = state.skipTerrain ? null : state.terrain;
widget.minToolDiam = minToolDiam;
widget.maxToolDiam = maxToolDiam;
ondone();
};
CAM.addDogbones = function(poly, dist, reverse) {
if (Array.isArray(poly)) {
return poly.forEach(p => CAM.addDogbones(p, dist));
}
if (poly.open) return;
let isCW = poly.isClockwise();
if (reverse || poly.parent) isCW = !isCW;
let oldpts = poly.points.slice();
let lastpt = oldpts[oldpts.length - 1];
let lastsl = lastpt.slopeTo(oldpts[0]).toUnit();
let newpts = [ ];
for (let i=0; i<oldpts.length + 1; i++) {
let nextpt = oldpts[i % oldpts.length];
let nextsl = lastpt.slopeTo(nextpt).toUnit();
let adiff = lastsl.angleDiff(nextsl, true);
let bdiff = ((adiff < 0 ? (180 - adiff) : (180 + adiff)) / 2) + 180;
if (isCW && adiff > 45) {
let newa = base.newSlopeFromAngle(lastsl.angle + bdiff);
newpts.push(lastpt.projectOnSlope(newa, dist));
newpts.push(lastpt.clone());
} else if (!isCW && adiff < -45) {
let newa = base.newSlopeFromAngle(lastsl.angle - bdiff);
newpts.push(lastpt.projectOnSlope(newa, dist));
newpts.push(lastpt.clone());
}
lastsl = nextsl;
lastpt = nextpt;
if (i < oldpts.length) {
newpts.push(nextpt);
}
}
poly.points = newpts;
if (poly.inner) {
CAM.addDogbones(poly.inner, dist, true);
}
};
CAM.traces = function(settings, widget, single) {
if (widget.traces && widget.trace_single === single) {
// do no work if cached
return false;
}
let slicer = new kiri.cam_slicer(widget.getPoints(), {
zlist: true,
zline: true
});
let indices = [...new Set(Object.keys(slicer.zFlat)
.map(kv => parseFloat(kv).round(5))
.appendAll(Object.entries(slicer.zLine).map(ze => {
let [ zk, zv ] = ze;
return zv > 1 ? parseFloat(zk).round(5) : null;
})
.filter(v => v !== null)))]
.sort((a,b) => b - a);
let traces = [];
// find and trim polys (including open) to shadow
let oneach = (data, index, total) => {
if (single) {
for (let line of data.lines) {
if (line.p1.distTo2D(line.p2) > 1) {
traces.push(newPolygon().append(line.p1).append(line.p2).setOpen());
}
}
} else
base.polygons.flatten(data.tops,null,true).forEach(poly => {
poly.inner = null;
poly.parent = null;
let z = poly.getZ();
for (let i=0, il=traces.length; i<il; i++) {
let trace = traces[i];
// only compare polys farther apart in Z
if (Math.abs(z - trace.getZ()) > 0.01) {
continue;
}
// do not add duplicates
if (traces[i].isEquivalent(poly)) {
return;
}
}
traces.push(poly);
});
};
let opts = { each: oneach, over: false, flatoff: 0, edges: true, openok: true };
slicer.slice(indices, opts);
opts.over = true;
slicer.slice(indices, opts);
widget.traces = traces;
widget.trace_single = single;
return true;
};
function cutTabs(tabs, offset, z, inter) {
tabs = tabs.filter(tab => z < tab.pos.z + tab.dim.z/2).map(tab => tab.off).flat();
return cutPolys(tabs, offset, z, false);
}
function cutPolys(polys, offset, z, inter) {
let noff = [];
offset.forEach(op => noff.appendAll( op.cut(POLY.union(polys, 0, true), inter) ));
return healPolys(noff);
}
function healPolys(noff) {
if (noff.length > 1) {
let heal = 0;
// heal/rejoin open segments that share endpoints
outer: for(;; heal++) {
let ntmp = noff, tlen = ntmp.length;
for (let i=0; i<tlen; i++) {
let s1 = ntmp[i];
if (!s1) continue;
for (let j=i+1; j<tlen; j++) {
let s2 = ntmp[j];
if (!s2) continue;
// require polys at same Z to heal
if (Math.abs(s1.getZ() - s2.getZ()) > 0.01) {
continue; continue;
} }
// do not add duplicates if (!(s1.open && s2.open)) continue;
if (traces[i].isEquivalent(poly)) { if (s1.last().isMergable2D(s2.first())) {
return; s1.addPoints(s2.points.slice(1));
ntmp[j] = null;
continue outer;
}
if (s2.last().isMergable2D(s1.first())) {
s2.addPoints(s1.points.slice(1));
ntmp[i] = null;
continue outer;
}
if (s1.first().isMergable2D(s2.first())) {
s1.reverse();
s1.addPoints(s2.points.slice(1));
ntmp[j] = null;
continue outer;
}
if (s1.last().isMergable2D(s2.last())) {
s2.reverse();
s1.addPoints(s2.points.slice(1));
ntmp[j] = null;
continue outer;
} }
} }
traces.push(poly);
});
};
let opts = { each: oneach, over: false, flatoff: 0, edges: true, openok: true };
slicer.slice(indices, opts);
opts.over = true;
slicer.slice(indices, opts);
widget.traces = traces;
widget.trace_single = single;
return true;
};
function cutTabs(tabs, offset, z, inter) {
tabs = tabs.filter(tab => z < tab.pos.z + tab.dim.z/2).map(tab => tab.off).flat();
return cutPolys(tabs, offset, z, false);
}
function cutPolys(polys, offset, z, inter) {
let noff = [];
offset.forEach(op => noff.appendAll( op.cut(POLY.union(polys, 0, true), inter) ));
return healPolys(noff);
}
function healPolys(noff) {
if (noff.length > 1) {
let heal = 0;
// heal/rejoin open segments that share endpoints
outer: for(;; heal++) {
let ntmp = noff, tlen = ntmp.length;
for (let i=0; i<tlen; i++) {
let s1 = ntmp[i];
if (!s1) continue;
for (let j=i+1; j<tlen; j++) {
let s2 = ntmp[j];
if (!s2) continue;
// require polys at same Z to heal
if (Math.abs(s1.getZ() - s2.getZ()) > 0.01) {
continue;
}
if (!(s1.open && s2.open)) continue;
if (s1.last().isMergable2D(s2.first())) {
s1.addPoints(s2.points.slice(1));
ntmp[j] = null;
continue outer;
}
if (s2.last().isMergable2D(s1.first())) {
s2.addPoints(s1.points.slice(1));
ntmp[i] = null;
continue outer;
}
if (s1.first().isMergable2D(s2.first())) {
s1.reverse();
s1.addPoints(s2.points.slice(1));
ntmp[j] = null;
continue outer;
}
if (s1.last().isMergable2D(s2.last())) {
s2.reverse();
s1.addPoints(s2.points.slice(1));
ntmp[j] = null;
continue outer;
}
}
}
break;
} }
if (heal > 0) { break;
// cull nulls }
noff = noff.filter(o => o); if (heal > 0) {
} // cull nulls
// close poly if head meets tail noff = noff.filter(o => o);
for (let poly of noff) { }
if (poly.open && poly.first().isMergable2D(poly.last())) { // close poly if head meets tail
poly.points.pop(); for (let poly of noff) {
poly.open = false; if (poly.open && poly.first().isMergable2D(poly.last())) {
} poly.points.pop();
poly.open = false;
} }
} }
return noff;
} }
return noff;
}
})(); });

File diff suppressed because it is too large Load diff

View file

@ -2,134 +2,135 @@
"use strict"; "use strict";
(function() { gapp.register("kiri-mode.cam.tools", [], (root, exports) => {
let KIRI = self.kiri, const { kiri } = root;
BASE = self.base, const { driver } = kiri;
UTIL = BASE.util, const { CAM } = driver;
HPI = Math.PI/2,
CAM = KIRI.driver.CAM;
class Tool { const HPI = Math.PI/2;
constructor(settings, id, number) {
if (number >= 0) { class Tool {
this.tool = settings.tools.filter(tool => tool.number == number)[0]; constructor(settings, id, number) {
} else { if (number >= 0) {
this.tool = settings.tools.filter(tool => tool.id == id)[0]; this.tool = settings.tools.filter(tool => tool.number == number)[0];
} } else {
if (!this.tool) { this.tool = settings.tools.filter(tool => tool.id == id)[0];
this.tool = Object.assign({}, settings.tools[0]);
this.tool.number = number >= 0 ? number : this.tool.number;
this.tool.id = id >= 0 ? id : this.tool.id;
}
} }
if (!this.tool) {
getID() { this.tool = Object.assign({}, settings.tools[0]);
return this.tool.id; this.tool.number = number >= 0 ? number : this.tool.number;
} this.tool.id = id >= 0 ? id : this.tool.id;
getName() {
return this.tool.name;
}
getType() {
return this.tool.type;
}
getNumber() {
return this.tool.number;
}
isMetric() {
return this.tool.metric;
}
unitScale() {
return this.isMetric() ? 1 : 25.4;
}
fluteLength() {
return this.unitScale() * this.tool.flute_len;
}
fluteDiameter() {
return this.unitScale() * this.tool.flute_diam;
}
tipDiameter() {
return this.unitScale() * this.tool.taper_tip;
}
shaftLength() {
return this.unitScale() * this.tool.shaft_len;
}
shaftDiameter() {
return this.unitScale() * this.tool.shaft_diam;
}
generateProfile(resolution) {
// generate tool profile
let type = this.getType(),
ball = type === "ballmill",
taper = type === "tapermill",
tip_diameter = this.tipDiameter(),
shaft_offset = this.fluteLength(),
flute_diameter = this.fluteDiameter(),
shaft_diameter = this.shaftDiameter(),
shaft_radius = shaft_diameter / 2,
shaft_pix_float = shaft_diameter / resolution,
shaft_pix_int = Math.round(shaft_pix_float),
shaft_radius_pix_float = shaft_pix_float / 2,
flute_radius = flute_diameter / 2,
flute_pix_float = flute_diameter / resolution,
flute_radius_pix_float = flute_pix_float / 2,
tip_pix_float = tip_diameter / resolution,
tip_radius_pix_float = tip_pix_float / 2,
tip_max_radius_offset = flute_radius_pix_float - tip_radius_pix_float,
profile_pix_iter = shaft_pix_int + (1 - shaft_pix_int % 2),
toolCenter = (shaft_pix_int - (shaft_pix_int % 2)) / 2,
toolOffset = [],
larger_shaft = shaft_diameter - flute_diameter > 0.001,
rpixsq = flute_radius_pix_float * flute_radius_pix_float;
// for each point in tool profile, check inside radius
for (let x = 0; x < profile_pix_iter; x++) {
for (let y = 0; y < profile_pix_iter; y++) {
let dx = x - toolCenter,
dy = y - toolCenter,
dist_from_center = Math.sqrt(dx * dx + dy * dy);
if (dist_from_center <= flute_radius_pix_float) {
// console.log({x,y,dx,dy,dist:dist_from_center,ln:dbl.length})
// flute offset points
let z_offset = 0;
if (ball) {
let rd = dist_from_center * dist_from_center;
z_offset = Math.sqrt(rpixsq - rd) * resolution - flute_radius;
// z_offset = (1 - Math.cos((dist_from_center / flute_radius_pix_float) * HPI)) * -flute_radius;
} else if (taper && dist_from_center >= tip_radius_pix_float) {
z_offset = ((dist_from_center - tip_radius_pix_float) / tip_max_radius_offset) * -shaft_offset;
}
toolOffset.push(dx, dy, z_offset);
} else if (shaft_offset && larger_shaft && dist_from_center <= shaft_radius_pix_float) {
// shaft offset points
toolOffset.push(dx, dy, -shaft_offset);
}
}
}
this.profile = toolOffset;
this.profileDim = {
size: shaft_diameter,
pix: profile_pix_iter + 2
};
return this;
} }
} }
CAM.Tool = Tool; getID() {
CAM.getToolDiameter = function(settings, id) { return this.tool.id;
return new CAM.Tool(settings, id).fluteDiameter(); }
};
})(); getName() {
return this.tool.name;
}
getType() {
return this.tool.type;
}
getNumber() {
return this.tool.number;
}
isMetric() {
return this.tool.metric;
}
unitScale() {
return this.isMetric() ? 1 : 25.4;
}
fluteLength() {
return this.unitScale() * this.tool.flute_len;
}
fluteDiameter() {
return this.unitScale() * this.tool.flute_diam;
}
tipDiameter() {
return this.unitScale() * this.tool.taper_tip;
}
shaftLength() {
return this.unitScale() * this.tool.shaft_len;
}
shaftDiameter() {
return this.unitScale() * this.tool.shaft_diam;
}
generateProfile(resolution) {
// generate tool profile
let type = this.getType(),
ball = type === "ballmill",
taper = type === "tapermill",
tip_diameter = this.tipDiameter(),
shaft_offset = this.fluteLength(),
flute_diameter = this.fluteDiameter(),
shaft_diameter = this.shaftDiameter(),
shaft_radius = shaft_diameter / 2,
shaft_pix_float = shaft_diameter / resolution,
shaft_pix_int = Math.round(shaft_pix_float),
shaft_radius_pix_float = shaft_pix_float / 2,
flute_radius = flute_diameter / 2,
flute_pix_float = flute_diameter / resolution,
flute_radius_pix_float = flute_pix_float / 2,
tip_pix_float = tip_diameter / resolution,
tip_radius_pix_float = tip_pix_float / 2,
tip_max_radius_offset = flute_radius_pix_float - tip_radius_pix_float,
profile_pix_iter = shaft_pix_int + (1 - shaft_pix_int % 2),
toolCenter = (shaft_pix_int - (shaft_pix_int % 2)) / 2,
toolOffset = [],
larger_shaft = shaft_diameter - flute_diameter > 0.001,
rpixsq = flute_radius_pix_float * flute_radius_pix_float;
// for each point in tool profile, check inside radius
for (let x = 0; x < profile_pix_iter; x++) {
for (let y = 0; y < profile_pix_iter; y++) {
let dx = x - toolCenter,
dy = y - toolCenter,
dist_from_center = Math.sqrt(dx * dx + dy * dy);
if (dist_from_center <= flute_radius_pix_float) {
// console.log({x,y,dx,dy,dist:dist_from_center,ln:dbl.length})
// flute offset points
let z_offset = 0;
if (ball) {
let rd = dist_from_center * dist_from_center;
z_offset = Math.sqrt(rpixsq - rd) * resolution - flute_radius;
// z_offset = (1 - Math.cos((dist_from_center / flute_radius_pix_float) * HPI)) * -flute_radius;
} else if (taper && dist_from_center >= tip_radius_pix_float) {
z_offset = ((dist_from_center - tip_radius_pix_float) / tip_max_radius_offset) * -shaft_offset;
}
toolOffset.push(dx, dy, z_offset);
} else if (shaft_offset && larger_shaft && dist_from_center <= shaft_radius_pix_float) {
// shaft offset points
toolOffset.push(dx, dy, -shaft_offset);
}
}
}
this.profile = toolOffset;
this.profileDim = {
size: shaft_diameter,
pix: profile_pix_iter + 2
};
return this;
}
}
CAM.Tool = Tool;
CAM.getToolDiameter = function(settings, id) {
return new CAM.Tool(settings, id).fluteDiameter();
};
});

View file

@ -2,404 +2,401 @@
"use strict"; "use strict";
(function() { gapp.register("kiri-mode.cam.tools", [], (root, exports) => {
let KIRI = self.kiri, const { base, kiri } = root;
BASE = self.base, const { driver, newSlice } = kiri;
CAM = KIRI.driver.CAM, const { CAM } = driver;
PRO = CAM.process, const { polygons, newLine, newSlope, newPoint, newPolygon } = base;
POLY = BASE.polygons,
RAD2DEG = 180 / Math.PI,
newSlice = KIRI.newSlice,
newLine = BASE.newLine,
newSlope = BASE.newSlope,
newPoint = BASE.newPoint,
newPolygon = BASE.newPolygon;
class Topo { const PRO = CAM.process;
constructor(opt = {}) { const POLY = polygons;
let { state, contour, onupdate, ondone } = opt; const RAD2DEG = 180 / Math.PI;
let { widget, settings, tshadow, center, tabs, color } = opt.state;
let density = parseInt(settings.controller.animesh || 100) * 2500, class Topo {
axis = contour.axis.toLowerCase(), constructor(opt = {}) {
contourX = axis === "x", let { state, contour, onupdate, ondone } = opt;
contourY = axis === "y", let { widget, settings, tshadow, center, tabs, color } = opt.state;
bounds = widget.getBoundingBox().clone(),
tolerance = contour.tolerance,
proc = settings.process,
shadow = tshadow,
minX = bounds.min.x,
maxX = bounds.max.x,
minY = bounds.min.y,
maxY = bounds.max.y,
zBottom = proc.camZBottom,
zMin = Math.max(bounds.min.z, zBottom) + 0.0001,
boundsX = maxX - minX,
boundsY = maxY - minY,
inside = contour.inside,
resolution = tolerance ? tolerance : 1/Math.sqrt(density/(boundsX * boundsY)),
tool = new CAM.Tool(settings, contour.tool),
toolOffset = tool.generateProfile(resolution).profile,
toolDiameter = tool.fluteDiameter(),
toolStep = toolDiameter * contour.step,
traceJoin = toolDiameter / 2,
maxangle = contour.angle,
curvesOnly = contour.curves,
R2A = 180 / Math.PI,
stepsx = Math.ceil(boundsX / resolution),
stepsy = Math.ceil(boundsY / resolution),
topo = widget.topo = {
data: new Float32Array(stepsx * stepsy),
stepsx: stepsx,
stepsy: stepsy,
bounds: bounds,
diameter: toolDiameter,
resolution: resolution,
profile: toolOffset,
widget: widget,
raster: true,
slices: null
},
data = topo.data,
newslices = [],
tabsMax = tabs ? Math.max(...tabs.map(tab => tab.dim.z/2 + tab.pos.z)) : 0,
tabsOn = tabs,
tabHeight = Math.max(proc.camTabsHeight + zBottom, tabsMax),
clipTab = tabsOn ? [] : null,
clipTo = inside ? shadow : POLY.expand(shadow, toolDiameter/2 + resolution * 3),
partOff = inside ? 0 : toolDiameter / 2 + resolution,
gridDelta = Math.floor(partOff / resolution),
debug = false,
debug_clips = debug && true,
debug_topo = debug && true,
debug_topo_lines = debug && true,
debug_topo_shells = debug && true,
time = Date.now;
if (tolerance == 0) { let density = parseInt(settings.controller.animesh || 100) * 2500,
console.log(`contour auto tolerance`,resolution.round(4)); axis = contour.axis.toLowerCase(),
} contourX = axis === "x",
contourY = axis === "y",
bounds = widget.getBoundingBox().clone(),
tolerance = contour.tolerance,
proc = settings.process,
shadow = tshadow,
minX = bounds.min.x,
maxX = bounds.max.x,
minY = bounds.min.y,
maxY = bounds.max.y,
zBottom = proc.camZBottom,
zMin = Math.max(bounds.min.z, zBottom) + 0.0001,
boundsX = maxX - minX,
boundsY = maxY - minY,
inside = contour.inside,
resolution = tolerance ? tolerance : 1/Math.sqrt(density/(boundsX * boundsY)),
tool = new CAM.Tool(settings, contour.tool),
toolOffset = tool.generateProfile(resolution).profile,
toolDiameter = tool.fluteDiameter(),
toolStep = toolDiameter * contour.step,
traceJoin = toolDiameter / 2,
maxangle = contour.angle,
curvesOnly = contour.curves,
R2A = 180 / Math.PI,
stepsx = Math.ceil(boundsX / resolution),
stepsy = Math.ceil(boundsY / resolution),
topo = widget.topo = {
data: new Float32Array(stepsx * stepsy),
stepsx: stepsx,
stepsy: stepsy,
bounds: bounds,
diameter: toolDiameter,
resolution: resolution,
profile: toolOffset,
widget: widget,
raster: true,
slices: null
},
data = topo.data,
newslices = [],
tabsMax = tabs ? Math.max(...tabs.map(tab => tab.dim.z/2 + tab.pos.z)) : 0,
tabsOn = tabs,
tabHeight = Math.max(proc.camTabsHeight + zBottom, tabsMax),
clipTab = tabsOn ? [] : null,
clipTo = inside ? shadow : POLY.expand(shadow, toolDiameter/2 + resolution * 3),
partOff = inside ? 0 : toolDiameter / 2 + resolution,
gridDelta = Math.floor(partOff / resolution),
debug = false,
debug_clips = debug && true,
debug_topo = debug && true,
debug_topo_lines = debug && true,
debug_topo_shells = debug && true,
time = Date.now;
this.tolerance = resolution; if (tolerance == 0) {
console.log(`contour auto tolerance`,resolution.round(4));
}
if (tabs) { this.tolerance = resolution;
clipTab.appendAll(tabs.map(tab => {
let ctab = POLY.expand([tab.poly], toolDiameter/2);
ctab.forEach(ct => ct.z = tab.dim.z/2 + tab.pos.z);
return ctab;
}).flat());
}
// debug clipTab and clipTo if (tabs) {
if (debug_clips) { clipTab.appendAll(tabs.map(tab => {
const debug = newSlice(-1); let ctab = POLY.expand([tab.poly], toolDiameter/2);
const output = debug.output(); ctab.forEach(ct => ct.z = tab.dim.z/2 + tab.pos.z);
// if (clipTab) output.setLayer("clip.tab", { line: 0xff0000 }).addPolys(clipTab); return ctab;
if (clipTo) output.setLayer("clip.to", { line: 0x00dd00 }).addPolys(clipTo); }).flat());
newslices.push(debug); }
}
let newtrace, // debug clipTab and clipTo
sliceout, if (debug_clips) {
latent, const debug = newSlice(-1);
lastP, const output = debug.output();
slice, lx, ly, // if (clipTab) output.setLayer("clip.tab", { line: 0xff0000 }).addPolys(clipTab);
startTime = time(), if (clipTo) output.setLayer("clip.to", { line: 0x00dd00 }).addPolys(clipTo);
stepsTaken = 0, newslices.push(debug);
stepsTotal = 0; }
// return the touching z given topo x,y and a tool profile let newtrace,
function toolAtZ(x,y) { sliceout,
const profile = toolOffset, latent,
sx = stepsx, lastP,
sy = stepsy, slice, lx, ly,
xl = sx - 1, startTime = time(),
yl = sy - 1; stepsTaken = 0,
stepsTotal = 0;
let tx, ty, tz, gv, i = 0, mz = -1; // return the touching z given topo x,y and a tool profile
function toolAtZ(x,y) {
const profile = toolOffset,
sx = stepsx,
sy = stepsy,
xl = sx - 1,
yl = sy - 1;
while (i < profile.length) { let tx, ty, tz, gv, i = 0, mz = -1;
// tool profile point x, y, and z offsets
const tx = profile[i++] + x;
const ty = profile[i++] + y;
const tz = profile[i++];
if (tx < 0 || tx > xl || ty < 0 || ty > yl) {
// if outside max topo steps, use zMin
gv = zMin;
} else {
// lookup grid value @ tx, ty
gv = topo.data[tx * sy + ty] || zMin;
}
// update the rest
mz = Math.max(tz + gv, mz);
}
return Math.max(mz, 0); while (i < profile.length) {
} // tool profile point x, y, and z offsets
const tx = profile[i++] + x;
function push_point(x,y,z) { const ty = profile[i++] + y;
const newP = newPoint(x,y,z); const tz = profile[i++];
// todo: merge co-linear, not just co-planar if (tx < 0 || tx > xl || ty < 0 || ty > yl) {
if (lastP && Math.abs(lastP.z - z) < 0.005) { // if outside max topo steps, use zMin
if (curvesOnly) { gv = zMin;
end_poly();
} else {
latent = newP;
}
} else { } else {
if (latent) { // lookup grid value @ tx, ty
newtrace.push(latent); gv = topo.data[tx * sy + ty] || zMin;
latent = null;
}
if (curvesOnly && lastP) {
// maxangle
const dz = Math.abs(lastP.z - z);
const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
const angle = Math.atan2(dz, dv) * RAD2DEG;
// if (lastP.z < 0.1) console.log('pp', {dz, dxy, angle});
if (angle > maxangle) {
lastP = newP;
return;
}
}
newtrace.push(newP);
} }
lastP = newP; // update the rest
mz = Math.max(tz + gv, mz);
} }
function end_poly() { return Math.max(mz, 0);
}
function push_point(x,y,z) {
const newP = newPoint(x,y,z);
// todo: merge co-linear, not just co-planar
if (lastP && Math.abs(lastP.z - z) < 0.005) {
if (curvesOnly) {
end_poly();
} else {
latent = newP;
}
} else {
if (latent) { if (latent) {
newtrace.push(latent); newtrace.push(latent);
latent = null;
} }
if (newtrace.length > 0) { if (curvesOnly && lastP) {
// add additional constraint on min perimeter() // maxangle
if (newtrace.length > 1) { const dz = Math.abs(lastP.z - z);
sliceout.push(newtrace); const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
const angle = Math.atan2(dz, dv) * RAD2DEG;
// if (lastP.z < 0.1) console.log('pp', {dz, dxy, angle});
if (angle > maxangle) {
lastP = newP;
return;
} }
newtrace = newPolygon().setOpen();
} }
latent = undefined; newtrace.push(newP);
lastP = undefined;
} }
lastP = newP;
}
function raster(slices) { function end_poly() {
if (!topo.raster) { if (latent) {
// console.log({skipping_raster: widget.id}); newtrace.push(latent);
return; }
if (newtrace.length > 0) {
// add additional constraint on min perimeter()
if (newtrace.length > 1) {
sliceout.push(newtrace);
} }
topo.raster = false; newtrace = newPolygon().setOpen();
}
latent = undefined;
lastP = undefined;
}
let gridx = 0, function raster(slices) {
gridy, if (!topo.raster) {
gridi, // index // console.log({skipping_raster: widget.id});
gridv, // value return;
i, il, j, jl, x, y, tv, ltv; }
topo.raster = false;
// filter lines pairs to only surface "up-facing", "uncovered" lines let gridx = 0,
slices.map(slice => { gridy,
newslices.push(slice); gridi, // index
const points = []; gridv, // value
// emit an array of valid line-pairs i, il, j, jl, x, y, tv, ltv;
const lines = slice.lines;
const len = lines.length; // filter lines pairs to only surface "up-facing", "uncovered" lines
outer: for (let i=0; i<len; i++) { slices.map(slice => {
let l1 = lines[i], p1 = l1.p1, p2 = l1.p2; newslices.push(slice);
// eliminate vertical const points = [];
if (p1.y === p2.y) continue; // emit an array of valid line-pairs
// eliminate if both points below cutoff const lines = slice.lines;
if (p1.z < zMin && p2.z < zMin) continue; const len = lines.length;
// sort p1,p2 by y for comparison outer: for (let i=0; i<len; i++) {
if (p1.y > p2.y) { const tp = p1; p1 = p2; p2 = tp }; let l1 = lines[i], p1 = l1.p1, p2 = l1.p2;
// eliminate if points "under" other lines // eliminate vertical
for (let j=0; j<len; j++) { if (p1.y === p2.y) continue;
// skip self and adjacent // eliminate if both points below cutoff
if (j >= i-1 && j <= i+1) continue; if (p1.z < zMin && p2.z < zMin) continue;
let l2 = lines[j], p3 = l2.p1, p4 = l2.p2; // sort p1,p2 by y for comparison
// sort p3,p4 by y for comparison if (p1.y > p2.y) { const tp = p1; p1 = p2; p2 = tp };
if (p3.y > p4.y) { const tp = p3; p3 = p4; p4 = tp }; // eliminate if points "under" other lines
// it's under the other line for (let j=0; j<len; j++) {
if (Math.max(p1.z,p2.z) < Math.min(p3.z,p4.z)) { // skip self and adjacent
// it's inside the other line, too, so skip if (j >= i-1 && j <= i+1) continue;
if (p1.y >= p3.y && p2.y <= p4.y) continue outer; let l2 = lines[j], p3 = l2.p1, p4 = l2.p2;
} // sort p3,p4 by y for comparison
if (p3.y > p4.y) { const tp = p3; p3 = p4; p4 = tp };
// it's under the other line
if (Math.max(p1.z,p2.z) < Math.min(p3.z,p4.z)) {
// it's inside the other line, too, so skip
if (p1.y >= p3.y && p2.y <= p4.y) continue outer;
} }
points.push(p1, p2);
} }
slice.points = points; points.push(p1, p2);
if (debug_topo_lines) slice.output() }
.setLayer("topo lines", {face: 0xff00ff, line: 0x880088}) slice.points = points;
.addLines(points); if (debug_topo_lines) slice.output()
if (debug_topo_shells) slice.output() .setLayer("topo lines", {face: 0xff00ff, line: 0x880088})
.setLayer("topo shells", {face: 0xff00ff, line: 0x008888}) .addLines(points);
.addPolys(slice.topPolys()); if (debug_topo_shells) slice.output()
}); .setLayer("topo shells", {face: 0xff00ff, line: 0x008888})
.addPolys(slice.topPolys());
});
// raster grid: for each Y slice, find z grid value (x/z swapped) // raster grid: for each Y slice, find z grid value (x/z swapped)
for (j=0, jl=slices.length; j<jl; j++) { for (j=0, jl=slices.length; j<jl; j++) {
const slice = slices[j]; const slice = slices[j];
const points = slice.points; const points = slice.points;
gridy = 0; gridy = 0;
// rasterize one x slice // rasterize one x slice
for (y = minY; y < maxY && gridy < stepsy; y += resolution) { for (y = minY; y < maxY && gridy < stepsy; y += resolution) {
gridi = gridx * stepsy + gridy; gridi = gridx * stepsy + gridy;
gridv = data[gridi] || 0; gridv = data[gridi] || 0;
// strategy using raw lines (faster slice, but more lines) // strategy using raw lines (faster slice, but more lines)
for (i=0, il=points.length; i<il; i += 2) { for (i=0, il=points.length; i<il; i += 2) {
const p1 = points[i], p2 = points[i+1]; const p1 = points[i], p2 = points[i+1];
// one endpoint above grid // one endpoint above grid
const crossz = (p1.z > gridv || p2.z > gridv); const crossz = (p1.z > gridv || p2.z > gridv);
// segment crosses grid y // segment crosses grid y
const spansy = (p1.y <= y && p2.y >= y); const spansy = (p1.y <= y && p2.y >= y);
if (crossz && spansy) { if (crossz && spansy) {
// compute intersection of z ray up // compute intersection of z ray up
// and segment at this grid point // and segment at this grid point
const dy = p1.y - p2.y, const dy = p1.y - p2.y,
dz = p1.z - p2.z, dz = p1.z - p2.z,
pct = (p1.y - y) / dy, pct = (p1.y - y) / dy,
nz = p1.z - (dz * pct); nz = p1.z - (dz * pct);
// save if point is greater than existing grid point // save if point is greater than existing grid point
if (nz > gridv) { if (nz > gridv) {
gridv = data[gridi] = Math.max(nz, zMin); gridv = data[gridi] = Math.max(nz, zMin);
if (debug_topo) slice.output() if (debug_topo) slice.output()
.setLayer("topo", {face: color, line: color}) .setLayer("topo", {face: color, line: color})
.addLine( .addLine(
newPoint(p1.x, y, 0), newPoint(p1.x, y, 0),
newPoint(p1.x, y, gridv) newPoint(p1.x, y, gridv)
); );
}
} }
} }
}
gridy++;
}
gridx++;
onupdate(++stepsTaken, stepsTotal, "raster surface");
}
}
function processSlices(slices) {
let gridx = 0,
gridy,
gridi, // index
gridv, // value
i, il, j, jl, x, y, tv, ltv;
raster(slices);
const checkr = newPoint(0,0);
const inClip = function(polys, checkZ) {
checkr.x = x;
checkr.y = y;
for (let i=0; i<polys.length; i++) {
let poly = polys[i];
let zok = checkZ ? checkZ <= poly.z : true;
tabZ = poly.z;
if (zok && checkr.isInPolygon(poly)) {
return true;
}
}
return false;
};
let tabZ;
if (contourY) {
startTime = time();
// emit slice per X
for (x = minX - partOff; x <= maxX + partOff; x += toolStep) {
gridx = Math.round(((x - minX) / boundsX) * stepsx);
ly = gridy = -gridDelta;
slice = newSlice(gridx);
newtrace = newPolygon().setOpen();
sliceout = [];
for (y = minY - partOff; y < maxY + partOff; y += resolution) {
// find tool z at grid point
tv = toolAtZ(gridx, gridy);
// when tabs are on and this point is inside the
// tab polygon, ensure z is at least tabHeight
if (tabsOn && tv < tabHeight && inClip(clipTab, tv)) {
tv = tabZ;//tabHeight;
}
// if the value is on the floor and inside the clip
// poly (usually shadow), end the segment
if (clipTo && !inClip(clipTo)) {
end_poly();
gridy++;
ly = -gridDelta;
continue;
}
push_point(x,y,tv);
ly = y;
ltv = tv;
gridy++; gridy++;
} }
gridx++; end_poly();
onupdate(++stepsTaken, stepsTotal, "raster surface"); if (sliceout.length > 0) {
newslices.push(slice);
slice.camLines = sliceout;
slice.output()
.setLayer("contour y", {face: color, line: color})
.addPolys(sliceout);
}
onupdate(++stepsTaken, stepsTotal, "contour y");
} }
} }
function processSlices(slices) { if (contourX) {
let gridx = 0, startTime = time();
gridy, // emit slice per Y
gridi, // index for (y = minY - partOff; y <= maxY + partOff; y += toolStep) {
gridv, // value gridy = Math.round(((y - minY) / boundsY) * stepsy);
i, il, j, jl, x, y, tv, ltv; lx = gridx = -gridDelta;
slice = newSlice(gridy);
raster(slices); newtrace = newPolygon().setOpen();
sliceout = [];
const checkr = newPoint(0,0); for (x = minX - partOff; x <= maxX + partOff; x += resolution) {
const inClip = function(polys, checkZ) { tv = toolAtZ(gridx, gridy);
checkr.x = x; if (tabsOn && tv < tabHeight && inClip(clipTab, tv)) {
checkr.y = y; tv = tabZ;
for (let i=0; i<polys.length; i++) {
let poly = polys[i];
let zok = checkZ ? checkZ <= poly.z : true;
tabZ = poly.z;
if (zok && checkr.isInPolygon(poly)) {
return true;
} }
} if (clipTo && !inClip(clipTo)) {
return false; end_poly();
};
let tabZ;
if (contourY) {
startTime = time();
// emit slice per X
for (x = minX - partOff; x <= maxX + partOff; x += toolStep) {
gridx = Math.round(((x - minX) / boundsX) * stepsx);
ly = gridy = -gridDelta;
slice = newSlice(gridx);
newtrace = newPolygon().setOpen();
sliceout = [];
for (y = minY - partOff; y < maxY + partOff; y += resolution) {
// find tool z at grid point
tv = toolAtZ(gridx, gridy);
// when tabs are on and this point is inside the
// tab polygon, ensure z is at least tabHeight
if (tabsOn && tv < tabHeight && inClip(clipTab, tv)) {
tv = tabZ;//tabHeight;
}
// if the value is on the floor and inside the clip
// poly (usually shadow), end the segment
if (clipTo && !inClip(clipTo)) {
end_poly();
gridy++;
ly = -gridDelta;
continue;
}
push_point(x,y,tv);
ly = y;
ltv = tv;
gridy++;
}
end_poly();
if (sliceout.length > 0) {
newslices.push(slice);
slice.camLines = sliceout;
slice.output()
.setLayer("contour y", {face: color, line: color})
.addPolys(sliceout);
}
onupdate(++stepsTaken, stepsTotal, "contour y");
}
}
if (contourX) {
startTime = time();
// emit slice per Y
for (y = minY - partOff; y <= maxY + partOff; y += toolStep) {
gridy = Math.round(((y - minY) / boundsY) * stepsy);
lx = gridx = -gridDelta;
slice = newSlice(gridy);
newtrace = newPolygon().setOpen();
sliceout = [];
for (x = minX - partOff; x <= maxX + partOff; x += resolution) {
tv = toolAtZ(gridx, gridy);
if (tabsOn && tv < tabHeight && inClip(clipTab, tv)) {
tv = tabZ;
}
if (clipTo && !inClip(clipTo)) {
end_poly();
gridx++;
lx = -gridDelta;
continue;
}
push_point(x,y,tv);
lx = x;
ltv = tv;
gridx++; gridx++;
lx = -gridDelta;
continue;
} }
end_poly(); push_point(x,y,tv);
if (sliceout.length > 0) { lx = x;
newslices.push(slice); ltv = tv;
slice.camLines = sliceout; gridx++;
slice.output()
.setLayer("contour x", {face: color, line: color})
.addPolys(sliceout);
}
onupdate(++stepsTaken, stepsTotal, "contour x");
} }
end_poly();
if (sliceout.length > 0) {
newslices.push(slice);
slice.camLines = sliceout;
slice.output()
.setLayer("contour x", {face: color, line: color})
.addPolys(sliceout);
}
onupdate(++stepsTaken, stepsTotal, "contour x");
} }
ondone(newslices);
} }
let slicer = new KIRI.cam_slicer(widget.getPoints(), { ondone(newslices);
swapX: true, emptyok: true, notopok: true
});
let sindex = slicer.interval(resolution);
if (!topo.slices) stepsTotal += sindex.length * 2;
if (contourX) stepsTotal += Math.round((maxY-minY) / toolStep);
if (contourY) stepsTotal += Math.round((maxX-minX) / toolStep);
let slices = topo.slices = topo.slices || slicer.slice(sindex, { each: (data, index, total) => {
onupdate(++stepsTaken, stepsTotal, "topo slice");
}, genso: true });
processSlices(slices.filter(s => s.lines).map(data => data.slice));
} }
let slicer = new kiri.cam_slicer(widget.getPoints(), {
swapX: true, emptyok: true, notopok: true
});
let sindex = slicer.interval(resolution);
if (!topo.slices) stepsTotal += sindex.length * 2;
if (contourX) stepsTotal += Math.round((maxY-minY) / toolStep);
if (contourY) stepsTotal += Math.round((maxX-minX) / toolStep);
let slices = topo.slices = topo.slices || slicer.slice(sindex, { each: (data, index, total) => {
onupdate(++stepsTaken, stepsTotal, "topo slice");
}, genso: true });
processSlices(slices.filter(s => s.lines).map(data => data.slice));
} }
}
CAM.Topo = Topo; CAM.Topo = Topo;
})(); });