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";
|
||||
|
||||
self.kiri.load(function() {
|
||||
// dep: kiri.api
|
||||
// dep: kiri-mode.cam.driver
|
||||
gapp.register("kiri-mode.cam.animate", [], (root, exports) => {
|
||||
|
||||
let KIRI = self.kiri,
|
||||
CAM = KIRI.driver.CAM,
|
||||
API, WORLD, SPACE,
|
||||
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 };
|
||||
const { kiri } = root;
|
||||
const { api, driver } = kiri;
|
||||
const { CAM } = driver;
|
||||
|
||||
// ---( 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)
|
||||
CAM.animate_clear = function(api) {
|
||||
API = api;
|
||||
KIRI.client.animate_cleanup();
|
||||
SPACE = KIRI.space;
|
||||
WORLD = SPACE.world;
|
||||
// ---( CLIENT FUNCTIONS )---
|
||||
|
||||
kiri.load(() => {
|
||||
if (!kiri.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { moto } = root;
|
||||
const { space } = moto;
|
||||
const { world } = space;
|
||||
|
||||
function animate_clear(api) {
|
||||
kiri.client.animate_cleanup();
|
||||
$('layer-animate').innerHTML = '';
|
||||
$('layer-toolpos').innerHTML = '';
|
||||
Object.keys(meshes).forEach(id => deleteMesh(id));
|
||||
}
|
||||
}
|
||||
|
||||
if (KIRI.client)
|
||||
CAM.animate = function(api, delay) {
|
||||
API = api;
|
||||
SPACE = KIRI.space;
|
||||
WORLD = SPACE.world;
|
||||
KIRI.client.animate_setup(API.conf.get(), data => {
|
||||
function animate(api, delay) {
|
||||
kiri.client.animate_setup(api.conf.get(), data => {
|
||||
checkMeshCommands(data);
|
||||
if (!(data && data.mesh_add)) {
|
||||
return;
|
||||
}
|
||||
const UC = API.uc;
|
||||
const UC = api.uc;
|
||||
const layer = $('layer-animate');
|
||||
layer.innerHTML = '';
|
||||
UC.setGroup(layer);
|
||||
|
|
@ -70,9 +74,109 @@ self.kiri.load(function() {
|
|||
toolPosZ = UC.newInput('z', {disabled: true, size: 7});
|
||||
playButton.style.display = '';
|
||||
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) {
|
||||
if (inc === Infinity) {
|
||||
|
|
@ -85,9 +189,9 @@ self.kiri.load(function() {
|
|||
}
|
||||
|
||||
function replay() {
|
||||
CAM.animate_clear(API);
|
||||
CAM.animate_clear(api);
|
||||
setTimeout(() => {
|
||||
CAM.animate(API, 50);
|
||||
CAM.animate(api, 50);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
|
|
@ -112,7 +216,7 @@ self.kiri.load(function() {
|
|||
mesh.position.x = pos.x;
|
||||
mesh.position.y = pos.y;
|
||||
mesh.position.z = pos.z;
|
||||
SPACE.update();
|
||||
space.update();
|
||||
if (id !== 0) {
|
||||
toolPosX.value = ((pos.x + posOffset.x) * 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));
|
||||
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;
|
||||
});
|
||||
|
||||
// ---( WORKER FUNCTIONS )---
|
||||
|
||||
kiri.load(() => {
|
||||
if (!kiri.worker) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 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 { process } = settings;
|
||||
const print = current.print;
|
||||
|
|
@ -267,6 +281,31 @@ self.kiri.load(function() {
|
|||
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) {
|
||||
const gridPoints = stepsX * stepsY;
|
||||
const pos = new Float32Array(gridPoints * 3);
|
||||
|
|
@ -300,33 +339,6 @@ self.kiri.load(function() {
|
|||
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 animating = false;
|
||||
let renderSpeed = 25;
|
||||
|
|
@ -507,10 +519,10 @@ self.kiri.load(function() {
|
|||
pos[(dx * pix + dy) * 3 + 2] = -dz;
|
||||
}
|
||||
send.data({ mesh_add: { id:++toolID, pos, ind }});
|
||||
}
|
||||
}
|
||||
|
||||
// load renderer code in worker context only
|
||||
if (KIRI.worker && false)
|
||||
if (kiri.worker && false)
|
||||
fetch('/wasm/kiri-ani.wasm')
|
||||
.then(response => response.arrayBuffer())
|
||||
.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";
|
||||
|
||||
(function() {
|
||||
// dep: main.kiri
|
||||
// use: kiri.api
|
||||
// use: kiri.widgets
|
||||
gapp.register("kiri-mode.cam.driver", [], (root, exports) => {
|
||||
|
||||
const KIRI = self.kiri,
|
||||
CAM = KIRI.driver.CAM = {
|
||||
// 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
|
||||
};
|
||||
const { kiri } = root;
|
||||
const { driver } = kiri;
|
||||
|
||||
// defer loading until KIRI.client and KIRI.worker exist
|
||||
KIRI.load(function(API) {
|
||||
const CAM = driver.CAM = {};
|
||||
|
||||
if (KIRI.client)
|
||||
CAM.traces = function(ondone, single) {
|
||||
KIRI.client.sync();
|
||||
let settings = API.conf.get();
|
||||
let widgets = API.widgets.map();
|
||||
KIRI.client.send("cam_traces", { settings, single }, output => {
|
||||
let ids = [];
|
||||
KIRI.codec.decode(output).forEach(rec => {
|
||||
ids.push(rec.id);
|
||||
widgets[rec.id].traces = rec.traces;
|
||||
});
|
||||
ondone(ids);
|
||||
});
|
||||
};
|
||||
CAM.process = {
|
||||
LEVEL: 1,
|
||||
ROUGH: 2,
|
||||
OUTLINE: 3,
|
||||
CONTOUR_X: 4,
|
||||
CONTOUR_Y: 5,
|
||||
TRACE: 6,
|
||||
DRILL: 7
|
||||
};
|
||||
|
||||
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,
|
||||
} } )));
|
||||
};
|
||||
// defer loading until kiri.client and kiri.worker exist
|
||||
kiri.load(api => {
|
||||
|
||||
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";
|
||||
|
||||
(function() {
|
||||
// dep: geo.base
|
||||
// dep: geo.polygons
|
||||
gapp.register("kiri-mode.cam.export", [], (root, exports) => {
|
||||
|
||||
let KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
POLY = BASE.polygons,
|
||||
UTIL = BASE.util,
|
||||
CAM = KIRI.driver.CAM,
|
||||
PRO = CAM.process;
|
||||
const { base, kiri } = root;
|
||||
const { polygons, util } = base;
|
||||
const { driver } = kiri;
|
||||
const { CAM } = driver;
|
||||
|
||||
/**
|
||||
* @returns {Array} gcode lines
|
||||
*/
|
||||
CAM.export = function(print, online) {
|
||||
let widget = print.widgets[0];
|
||||
if (!widget) return;
|
||||
const POLY = polygons;
|
||||
const PRO = CAM.process;
|
||||
|
||||
let i,
|
||||
time = 0,
|
||||
lines = 0,
|
||||
bytes = 0,
|
||||
opnum = 0,
|
||||
factor = 1,
|
||||
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
|
||||
}
|
||||
/**
|
||||
* @returns {Array} gcode lines
|
||||
*/
|
||||
CAM.export = function(print, online) {
|
||||
const widget = print.widgets[0];
|
||||
if (!widget) return;
|
||||
|
||||
function section(section) {
|
||||
append();
|
||||
online({ section });
|
||||
const { settings } = print;
|
||||
const { device, tools } = settings;
|
||||
|
||||
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) {
|
||||
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
|
||||
function section(section) {
|
||||
append();
|
||||
online({ section });
|
||||
}
|
||||
|
||||
print.time = time;
|
||||
print.lines = lines;
|
||||
print.bytes = bytes + lines - 1;
|
||||
print.bounds = runbox;
|
||||
};
|
||||
function append(line) {
|
||||
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(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";
|
||||
|
||||
(function() {
|
||||
// dep: geo.point
|
||||
// dep: geo.polygons
|
||||
// dep: kiri.slice
|
||||
gapp.register("kiri-mode.cam.slice", [], (root, exports) => {
|
||||
|
||||
let KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
CAM = KIRI.driver.CAM,
|
||||
OPS = CAM.OPS,
|
||||
PRO = CAM.process,
|
||||
POLY = BASE.polygons,
|
||||
newSlice = KIRI.newSlice,
|
||||
newPoint = BASE.newPoint,
|
||||
newPolygon = BASE.newPolygon;
|
||||
const { base, kiri } = root;
|
||||
const { driver, newSlice } = kiri;
|
||||
const { polygons, newPoint, newPolygon } = base;
|
||||
const { CAM } = driver;
|
||||
const { OPS } = CAM;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
const PRO = CAM.process;
|
||||
const POLY = polygons;
|
||||
|
||||
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 => {
|
||||
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 => {
|
||||
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());
|
||||
}
|
||||
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 });
|
||||
}
|
||||
} 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) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
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;
|
||||
}
|
||||
// do not add duplicates
|
||||
if (traces[i].isEquivalent(poly)) {
|
||||
return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// cull nulls
|
||||
noff = noff.filter(o => o);
|
||||
}
|
||||
// close poly if head meets tail
|
||||
for (let poly of noff) {
|
||||
if (poly.open && poly.first().isMergable2D(poly.last())) {
|
||||
poly.points.pop();
|
||||
poly.open = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (heal > 0) {
|
||||
// cull nulls
|
||||
noff = noff.filter(o => o);
|
||||
}
|
||||
// close poly if head meets tail
|
||||
for (let poly of noff) {
|
||||
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";
|
||||
|
||||
(function() {
|
||||
gapp.register("kiri-mode.cam.tools", [], (root, exports) => {
|
||||
|
||||
let KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
HPI = Math.PI/2,
|
||||
CAM = KIRI.driver.CAM;
|
||||
const { kiri } = root;
|
||||
const { driver } = kiri;
|
||||
const { CAM } = driver;
|
||||
|
||||
class Tool {
|
||||
constructor(settings, id, number) {
|
||||
if (number >= 0) {
|
||||
this.tool = settings.tools.filter(tool => tool.number == number)[0];
|
||||
} else {
|
||||
this.tool = settings.tools.filter(tool => tool.id == id)[0];
|
||||
}
|
||||
if (!this.tool) {
|
||||
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;
|
||||
}
|
||||
const HPI = Math.PI/2;
|
||||
|
||||
class Tool {
|
||||
constructor(settings, id, number) {
|
||||
if (number >= 0) {
|
||||
this.tool = settings.tools.filter(tool => tool.number == number)[0];
|
||||
} else {
|
||||
this.tool = settings.tools.filter(tool => tool.id == id)[0];
|
||||
}
|
||||
|
||||
getID() {
|
||||
return 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;
|
||||
if (!this.tool) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
CAM.Tool = Tool;
|
||||
CAM.getToolDiameter = function(settings, id) {
|
||||
return new CAM.Tool(settings, id).fluteDiameter();
|
||||
};
|
||||
getID() {
|
||||
return 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;
|
||||
|
||||
CAM.getToolDiameter = function(settings, id) {
|
||||
return new CAM.Tool(settings, id).fluteDiameter();
|
||||
};
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,404 +2,401 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
(function() {
|
||||
gapp.register("kiri-mode.cam.tools", [], (root, exports) => {
|
||||
|
||||
let KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
CAM = KIRI.driver.CAM,
|
||||
PRO = CAM.process,
|
||||
POLY = BASE.polygons,
|
||||
RAD2DEG = 180 / Math.PI,
|
||||
newSlice = KIRI.newSlice,
|
||||
newLine = BASE.newLine,
|
||||
newSlope = BASE.newSlope,
|
||||
newPoint = BASE.newPoint,
|
||||
newPolygon = BASE.newPolygon;
|
||||
const { base, kiri } = root;
|
||||
const { driver, newSlice } = kiri;
|
||||
const { CAM } = driver;
|
||||
const { polygons, newLine, newSlope, newPoint, newPolygon } = base;
|
||||
|
||||
class Topo {
|
||||
constructor(opt = {}) {
|
||||
let { state, contour, onupdate, ondone } = opt;
|
||||
let { widget, settings, tshadow, center, tabs, color } = opt.state;
|
||||
const PRO = CAM.process;
|
||||
const POLY = polygons;
|
||||
const RAD2DEG = 180 / Math.PI;
|
||||
|
||||
let density = parseInt(settings.controller.animesh || 100) * 2500,
|
||||
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;
|
||||
class Topo {
|
||||
constructor(opt = {}) {
|
||||
let { state, contour, onupdate, ondone } = opt;
|
||||
let { widget, settings, tshadow, center, tabs, color } = opt.state;
|
||||
|
||||
if (tolerance == 0) {
|
||||
console.log(`contour auto tolerance`,resolution.round(4));
|
||||
}
|
||||
let density = parseInt(settings.controller.animesh || 100) * 2500,
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
this.tolerance = resolution;
|
||||
|
||||
// debug clipTab and clipTo
|
||||
if (debug_clips) {
|
||||
const debug = newSlice(-1);
|
||||
const output = debug.output();
|
||||
// if (clipTab) output.setLayer("clip.tab", { line: 0xff0000 }).addPolys(clipTab);
|
||||
if (clipTo) output.setLayer("clip.to", { line: 0x00dd00 }).addPolys(clipTo);
|
||||
newslices.push(debug);
|
||||
}
|
||||
if (tabs) {
|
||||
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());
|
||||
}
|
||||
|
||||
let newtrace,
|
||||
sliceout,
|
||||
latent,
|
||||
lastP,
|
||||
slice, lx, ly,
|
||||
startTime = time(),
|
||||
stepsTaken = 0,
|
||||
stepsTotal = 0;
|
||||
// debug clipTab and clipTo
|
||||
if (debug_clips) {
|
||||
const debug = newSlice(-1);
|
||||
const output = debug.output();
|
||||
// if (clipTab) output.setLayer("clip.tab", { line: 0xff0000 }).addPolys(clipTab);
|
||||
if (clipTo) output.setLayer("clip.to", { line: 0x00dd00 }).addPolys(clipTo);
|
||||
newslices.push(debug);
|
||||
}
|
||||
|
||||
// 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;
|
||||
let newtrace,
|
||||
sliceout,
|
||||
latent,
|
||||
lastP,
|
||||
slice, lx, ly,
|
||||
startTime = time(),
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
let tx, ty, tz, gv, i = 0, mz = -1;
|
||||
|
||||
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;
|
||||
}
|
||||
while (i < profile.length) {
|
||||
// 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 {
|
||||
if (latent) {
|
||||
newtrace.push(latent);
|
||||
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);
|
||||
// lookup grid value @ tx, ty
|
||||
gv = topo.data[tx * sy + ty] || zMin;
|
||||
}
|
||||
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) {
|
||||
newtrace.push(latent);
|
||||
latent = null;
|
||||
}
|
||||
if (newtrace.length > 0) {
|
||||
// add additional constraint on min perimeter()
|
||||
if (newtrace.length > 1) {
|
||||
sliceout.push(newtrace);
|
||||
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 = newPolygon().setOpen();
|
||||
}
|
||||
latent = undefined;
|
||||
lastP = undefined;
|
||||
newtrace.push(newP);
|
||||
}
|
||||
lastP = newP;
|
||||
}
|
||||
|
||||
function raster(slices) {
|
||||
if (!topo.raster) {
|
||||
// console.log({skipping_raster: widget.id});
|
||||
return;
|
||||
function end_poly() {
|
||||
if (latent) {
|
||||
newtrace.push(latent);
|
||||
}
|
||||
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,
|
||||
gridy,
|
||||
gridi, // index
|
||||
gridv, // value
|
||||
i, il, j, jl, x, y, tv, ltv;
|
||||
function raster(slices) {
|
||||
if (!topo.raster) {
|
||||
// console.log({skipping_raster: widget.id});
|
||||
return;
|
||||
}
|
||||
topo.raster = false;
|
||||
|
||||
// filter lines pairs to only surface "up-facing", "uncovered" lines
|
||||
slices.map(slice => {
|
||||
newslices.push(slice);
|
||||
const points = [];
|
||||
// emit an array of valid line-pairs
|
||||
const lines = slice.lines;
|
||||
const len = lines.length;
|
||||
outer: for (let i=0; i<len; i++) {
|
||||
let l1 = lines[i], p1 = l1.p1, p2 = l1.p2;
|
||||
// eliminate vertical
|
||||
if (p1.y === p2.y) continue;
|
||||
// eliminate if both points below cutoff
|
||||
if (p1.z < zMin && p2.z < zMin) continue;
|
||||
// sort p1,p2 by y for comparison
|
||||
if (p1.y > p2.y) { const tp = p1; p1 = p2; p2 = tp };
|
||||
// eliminate if points "under" other lines
|
||||
for (let j=0; j<len; j++) {
|
||||
// skip self and adjacent
|
||||
if (j >= i-1 && j <= i+1) continue;
|
||||
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;
|
||||
}
|
||||
let gridx = 0,
|
||||
gridy,
|
||||
gridi, // index
|
||||
gridv, // value
|
||||
i, il, j, jl, x, y, tv, ltv;
|
||||
|
||||
// filter lines pairs to only surface "up-facing", "uncovered" lines
|
||||
slices.map(slice => {
|
||||
newslices.push(slice);
|
||||
const points = [];
|
||||
// emit an array of valid line-pairs
|
||||
const lines = slice.lines;
|
||||
const len = lines.length;
|
||||
outer: for (let i=0; i<len; i++) {
|
||||
let l1 = lines[i], p1 = l1.p1, p2 = l1.p2;
|
||||
// eliminate vertical
|
||||
if (p1.y === p2.y) continue;
|
||||
// eliminate if both points below cutoff
|
||||
if (p1.z < zMin && p2.z < zMin) continue;
|
||||
// sort p1,p2 by y for comparison
|
||||
if (p1.y > p2.y) { const tp = p1; p1 = p2; p2 = tp };
|
||||
// eliminate if points "under" other lines
|
||||
for (let j=0; j<len; j++) {
|
||||
// skip self and adjacent
|
||||
if (j >= i-1 && j <= i+1) continue;
|
||||
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;
|
||||
if (debug_topo_lines) slice.output()
|
||||
.setLayer("topo lines", {face: 0xff00ff, line: 0x880088})
|
||||
.addLines(points);
|
||||
if (debug_topo_shells) slice.output()
|
||||
.setLayer("topo shells", {face: 0xff00ff, line: 0x008888})
|
||||
.addPolys(slice.topPolys());
|
||||
});
|
||||
points.push(p1, p2);
|
||||
}
|
||||
slice.points = points;
|
||||
if (debug_topo_lines) slice.output()
|
||||
.setLayer("topo lines", {face: 0xff00ff, line: 0x880088})
|
||||
.addLines(points);
|
||||
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)
|
||||
for (j=0, jl=slices.length; j<jl; j++) {
|
||||
const slice = slices[j];
|
||||
const points = slice.points;
|
||||
gridy = 0;
|
||||
// rasterize one x slice
|
||||
for (y = minY; y < maxY && gridy < stepsy; y += resolution) {
|
||||
gridi = gridx * stepsy + gridy;
|
||||
gridv = data[gridi] || 0;
|
||||
// strategy using raw lines (faster slice, but more lines)
|
||||
for (i=0, il=points.length; i<il; i += 2) {
|
||||
const p1 = points[i], p2 = points[i+1];
|
||||
// one endpoint above grid
|
||||
const crossz = (p1.z > gridv || p2.z > gridv);
|
||||
// segment crosses grid y
|
||||
const spansy = (p1.y <= y && p2.y >= y);
|
||||
if (crossz && spansy) {
|
||||
// compute intersection of z ray up
|
||||
// and segment at this grid point
|
||||
const dy = p1.y - p2.y,
|
||||
dz = p1.z - p2.z,
|
||||
pct = (p1.y - y) / dy,
|
||||
nz = p1.z - (dz * pct);
|
||||
// save if point is greater than existing grid point
|
||||
if (nz > gridv) {
|
||||
gridv = data[gridi] = Math.max(nz, zMin);
|
||||
if (debug_topo) slice.output()
|
||||
.setLayer("topo", {face: color, line: color})
|
||||
.addLine(
|
||||
newPoint(p1.x, y, 0),
|
||||
newPoint(p1.x, y, gridv)
|
||||
);
|
||||
}
|
||||
// raster grid: for each Y slice, find z grid value (x/z swapped)
|
||||
for (j=0, jl=slices.length; j<jl; j++) {
|
||||
const slice = slices[j];
|
||||
const points = slice.points;
|
||||
gridy = 0;
|
||||
// rasterize one x slice
|
||||
for (y = minY; y < maxY && gridy < stepsy; y += resolution) {
|
||||
gridi = gridx * stepsy + gridy;
|
||||
gridv = data[gridi] || 0;
|
||||
// strategy using raw lines (faster slice, but more lines)
|
||||
for (i=0, il=points.length; i<il; i += 2) {
|
||||
const p1 = points[i], p2 = points[i+1];
|
||||
// one endpoint above grid
|
||||
const crossz = (p1.z > gridv || p2.z > gridv);
|
||||
// segment crosses grid y
|
||||
const spansy = (p1.y <= y && p2.y >= y);
|
||||
if (crossz && spansy) {
|
||||
// compute intersection of z ray up
|
||||
// and segment at this grid point
|
||||
const dy = p1.y - p2.y,
|
||||
dz = p1.z - p2.z,
|
||||
pct = (p1.y - y) / dy,
|
||||
nz = p1.z - (dz * pct);
|
||||
// save if point is greater than existing grid point
|
||||
if (nz > gridv) {
|
||||
gridv = data[gridi] = Math.max(nz, zMin);
|
||||
if (debug_topo) slice.output()
|
||||
.setLayer("topo", {face: color, line: color})
|
||||
.addLine(
|
||||
newPoint(p1.x, y, 0),
|
||||
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++;
|
||||
}
|
||||
gridx++;
|
||||
onupdate(++stepsTaken, stepsTotal, "raster surface");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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++;
|
||||
}
|
||||
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;
|
||||
if (clipTo && !inClip(clipTo)) {
|
||||
end_poly();
|
||||
gridx++;
|
||||
lx = -gridDelta;
|
||||
continue;
|
||||
}
|
||||
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");
|
||||
push_point(x,y,tv);
|
||||
lx = x;
|
||||
ltv = tv;
|
||||
gridx++;
|
||||
}
|
||||
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(), {
|
||||
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));
|
||||
ondone(newslices);
|
||||
}
|
||||
|
||||
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