update code style, use gapp.register() for cam driver
This commit is contained in:
parent
57a2129dba
commit
836d53aad5
10 changed files with 4881 additions and 4862 deletions
|
|
@ -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
|
|
@ -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,
|
||||||
|
} } )));
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
})();
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue