checkpoint rendering refactor. still broken beyond slicing
This commit is contained in:
parent
64380b6ef6
commit
9a3cf79c8b
11 changed files with 1099 additions and 1228 deletions
|
|
@ -92,12 +92,6 @@
|
|||
type: 'slice',
|
||||
z: this.z,
|
||||
index: this.index,
|
||||
camMode: this.camMode,
|
||||
tops: encode(this.tops, state),
|
||||
bridges: encode(this.bridges, state),
|
||||
flats: encode(this.flats, state),
|
||||
solids: encode(this.solids, state),
|
||||
supports: encode(this.supports, state),
|
||||
render: encode(this.render, state)
|
||||
};
|
||||
};
|
||||
|
|
@ -106,12 +100,6 @@
|
|||
let slice = KIRI.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
|
||||
|
||||
slice.index = v.index;
|
||||
slice.camMode = v.camMode;
|
||||
slice.tops = decode(v.tops, state);
|
||||
slice.bridges = decode(v.bridges, state);
|
||||
slice.flats = decode(v.flats, state);
|
||||
slice.solids = decode(v.solids, state);
|
||||
slice.supports = decode(v.supports, state);
|
||||
slice.render = decode(v.render, state)
|
||||
|
||||
return slice;
|
||||
|
|
@ -156,28 +144,28 @@
|
|||
return {
|
||||
type: 'top',
|
||||
poly: encode(this.poly, state),
|
||||
traces: encode(this.traces, state),
|
||||
inner: encode(this.inner, state),
|
||||
// thinner: encode(this.thinner, state),
|
||||
solids: encode(this.solids, state),
|
||||
thin_fill: encodePointArray(this.thin_fill, state),
|
||||
fill_lines: encodePointArray(this.fill_lines, state),
|
||||
fill_sparse: encode(this.fill_sparse, state),
|
||||
polish: encode(this.polish, state)
|
||||
// traces: encode(this.traces, state),
|
||||
// inner: encode(this.inner, state),
|
||||
// // thinner: encode(this.thinner, state),
|
||||
// solids: encode(this.solids, state),
|
||||
// thin_fill: encodePointArray(this.thin_fill, state),
|
||||
// fill_lines: encodePointArray(this.fill_lines, state),
|
||||
// fill_sparse: encode(this.fill_sparse, state),
|
||||
// polish: encode(this.polish, state)
|
||||
};
|
||||
};
|
||||
|
||||
registerDecoder('top', function(v, state) {
|
||||
let top = KIRI.newTop(decode(v.poly, state));
|
||||
|
||||
top.traces = decode(v.traces, state);
|
||||
top.inner = decode(v.inner, state);
|
||||
// top.thinner = decode(v.thinner, state);
|
||||
top.solids = decode(v.solids, state);
|
||||
top.thin_fill = decodePointArray(v.thin_fill, state);
|
||||
top.fill_lines = decodePointArray(v.fill_lines, state);
|
||||
top.fill_sparse = decode(v.fill_sparse, state);
|
||||
top.polish = decode(v.polish, state);
|
||||
// top.traces = decode(v.traces, state);
|
||||
// top.inner = decode(v.inner, state);
|
||||
// // top.thinner = decode(v.thinner, state);
|
||||
// top.solids = decode(v.solids, state);
|
||||
// top.thin_fill = decodePointArray(v.thin_fill, state);
|
||||
// top.fill_lines = decodePointArray(v.fill_lines, state);
|
||||
// top.fill_sparse = decode(v.fill_sparse, state);
|
||||
// top.polish = decode(v.polish, state);
|
||||
|
||||
return top;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1595,21 +1595,6 @@
|
|||
]),
|
||||
|
||||
layers: UC.setGroup($("layers")),
|
||||
layerTrace: UC.newBoolean(LANG.la_trce, onLayerToggle, {modes:FDM_LASER}),
|
||||
layerFacing: UC.newBoolean(LANG.la_face, onLayerToggle, {modes:[]}), // see sliceRender() in CAM
|
||||
layerRough: UC.newBoolean(LANG.la_ruff, onLayerToggle, {modes:CAM}),
|
||||
layerFinish: UC.newBoolean(LANG.la_fini, onLayerToggle, {modes:CAM}),
|
||||
layerFinishX: UC.newBoolean(LANG.la_finx, onLayerToggle, {modes:CAM}),
|
||||
layerFinishY: UC.newBoolean(LANG.la_finy, onLayerToggle, {modes:CAM}),
|
||||
layerFill: UC.newBoolean(LANG.la_fill, onLayerToggle, {modes:FDM}),
|
||||
layerSparse: UC.newBoolean(LANG.la_sprs, onLayerToggle, {modes:FDM}),
|
||||
layerSupport: UC.newBoolean(LANG.la_sprt, onLayerToggle, {modes:FDM}),
|
||||
layerOutline: UC.newBoolean(LANG.la_olin, onLayerToggle, {modes:GCODE}),
|
||||
layerDelta: UC.newBoolean(LANG.la_dlta, onLayerToggle, {modes:FDM}),
|
||||
layerSolid: UC.newBoolean(LANG.la_slds, onLayerToggle, {modes:FDM}),
|
||||
layerPrint: UC.newBoolean(LANG.la_prnt, onLayerToggle, {modes:GCODE}),
|
||||
layerMoves: UC.newBoolean(LANG.la_move, onLayerToggle, {modes:GCODE}),
|
||||
layerParts: UC.newBoolean(LANG.la_part, onLayerToggle, {modes:CAM})
|
||||
});
|
||||
|
||||
function showLaserKnife() {
|
||||
|
|
|
|||
|
|
@ -227,14 +227,6 @@
|
|||
LP.noodle = function(poly, offset, color, traceColor, open) {
|
||||
if (!poly) return;
|
||||
|
||||
// if (!Array.isArray(poly)) {
|
||||
// poly = [ poly ];
|
||||
// }
|
||||
// poly.forEach(p => {
|
||||
// this.add(color, {path: p, offset});
|
||||
// });
|
||||
// return;
|
||||
|
||||
if (Array.isArray(poly)) {
|
||||
poly = POLY.flatten(poly, [], true);
|
||||
} else {
|
||||
|
|
@ -427,4 +419,120 @@
|
|||
this.changed = false;
|
||||
}
|
||||
|
||||
Layer.renderSetup = function() {
|
||||
const API = KIRI.api, UC = API.uc, UI = API.ui;
|
||||
const dyn = UI.dyn = {};
|
||||
const layers = $("layers");
|
||||
UC.setGroup(layers);
|
||||
layers.innerHTML = '';
|
||||
return Layer;
|
||||
};
|
||||
|
||||
Layer.renderSlices = function(slices) {
|
||||
if (slices && slices.length) {
|
||||
slices.forEach(slice => Layer.renderSlice(slice));
|
||||
}
|
||||
return Layer;
|
||||
};
|
||||
|
||||
Layer.renderSlice = function(slice) {
|
||||
let group = new THREE.Group();
|
||||
let render = slice.render;
|
||||
let view = slice.view;
|
||||
|
||||
if (!render) return;
|
||||
|
||||
function addPoly(vertices, poly) {
|
||||
const points = poly.points, len = points.length;
|
||||
for (let i=1; i<len; i++) {
|
||||
const p1 = points[i-1], p2 = points[i];
|
||||
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
}
|
||||
if (!poly.open) {
|
||||
const p1 = points[len - 1], p2 = points[0];
|
||||
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
}
|
||||
}
|
||||
|
||||
const API = KIRI.api, UC = API.uc, UI = API.ui, DYN = UI.dyn;;
|
||||
|
||||
for (const [layer, data] of Object.entries(render.layers)) {
|
||||
if (!DYN[layer]) {
|
||||
DYN[layer] = {
|
||||
group: [],
|
||||
toggle: UC.newBoolean(layer, (abc) => {
|
||||
ctrl.group.forEach(mat => {
|
||||
mat.visible = ctrl.toggle.checked;
|
||||
});
|
||||
})
|
||||
};
|
||||
}
|
||||
const ctrl = DYN[layer];
|
||||
ctrl.toggle.checked = true;
|
||||
|
||||
const { polys, lines, faces, paths } = data;
|
||||
if (polys.length || lines.length) {
|
||||
const mat = new THREE.LineBasicMaterial({ color: data.color.line });
|
||||
const geo = new THREE.Geometry(), vert = geo.vertices;
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
addPoly(vert, polys[i]);
|
||||
}
|
||||
for (let i=0, il=lines.length; i<il; i++) {
|
||||
const p = lines[i];
|
||||
vert.push(new THREE.Vector3(p.x, p.y, p.z));
|
||||
}
|
||||
if (vert.length) {
|
||||
group.add(new THREE.LineSegments(geo, mat));
|
||||
}
|
||||
ctrl.group.push(mat);
|
||||
}
|
||||
if (faces.length) {
|
||||
const mat = new THREE.MeshPhongMaterial({
|
||||
transparent: true,
|
||||
shininess: 100,
|
||||
specular: 0x222222,
|
||||
opacity: 1,
|
||||
color: data.color.face,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
|
||||
geo.computeFaceNormals();
|
||||
geo.computeVertexNormals();
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
group.add(mesh);
|
||||
ctrl.group.push(mat);
|
||||
}
|
||||
if (paths.length) {
|
||||
const mat = new THREE.MeshPhongMaterial({
|
||||
transparent: true,
|
||||
shininess: 100,
|
||||
specular: 0x222222,
|
||||
opacity: 1,
|
||||
color: data.color.face,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
paths.forEach((path, i) => {
|
||||
const { index, faces, z } = path;
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
|
||||
geo.setIndex(index);
|
||||
geo.computeFaceNormals();
|
||||
geo.computeVertexNormals();
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.position.z = z;
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
group.add(mesh);
|
||||
});
|
||||
ctrl.group.push(mat);
|
||||
}
|
||||
}
|
||||
|
||||
slice.view.add(group);
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -599,9 +599,6 @@
|
|||
}
|
||||
|
||||
function setWidgetVisibility(bool) {
|
||||
if (bool) {
|
||||
UI.layerParts.checked = true;
|
||||
}
|
||||
forAllWidgets(w => {
|
||||
if (bool) {
|
||||
w.show();
|
||||
|
|
@ -621,10 +618,6 @@
|
|||
return;
|
||||
}
|
||||
|
||||
if (MODE === MODES.CAM) {
|
||||
setWidgetVisibility(UI.layerParts.checked);
|
||||
}
|
||||
|
||||
showSlider();
|
||||
|
||||
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
|
||||
|
|
@ -638,9 +631,7 @@
|
|||
API.var.layer_hi = layer;
|
||||
API.event.emit("slider.label");
|
||||
|
||||
let print = UI.layerPrint.checked,
|
||||
moves = UI.layerMoves.checked,
|
||||
cam = MODE === MODES.CAM,
|
||||
let cam = MODE === MODES.CAM,
|
||||
sla = MODE === MODES.SLA,
|
||||
hi = cam ? API.var.layer_max - API.var.layer_lo : API.var.layer_hi,
|
||||
lo = cam ? API.var.layer_max - API.var.layer_hi : API.var.layer_lo;
|
||||
|
|
@ -648,7 +639,7 @@
|
|||
updateSlider();
|
||||
|
||||
forAllWidgets(function(widget) {
|
||||
if (print) return widget.hideSlices();
|
||||
if (currentPrint) return widget.hideSlices();
|
||||
|
||||
let slices = widget.slices;
|
||||
if (!slices) return;
|
||||
|
|
@ -656,46 +647,16 @@
|
|||
for (let j = 0; j < slices.length; j++) {
|
||||
let slice = slices[j];
|
||||
slice.view.visible = j >= lo && j <= hi;
|
||||
let layers = slice.layers;
|
||||
layers.outline.setVisible(cam ?
|
||||
UI.layerOutline.checked :
|
||||
UI.layerOutline.checked
|
||||
);
|
||||
layers.trace.setVisible(cam ?
|
||||
UI.layerRough.checked :
|
||||
UI.layerTrace.checked
|
||||
);
|
||||
layers.bridge.setVisible(cam ?
|
||||
UI.layerFinishX.checked :
|
||||
UI.layerDelta.checked
|
||||
);
|
||||
layers.flat.setVisible(cam ?
|
||||
UI.layerFinishY.checked :
|
||||
UI.layerDelta.checked
|
||||
);
|
||||
layers.solid.setVisible(cam ?
|
||||
UI.layerFinish.checked :
|
||||
UI.layerSolid.checked
|
||||
);
|
||||
layers.fill.setVisible(cam ?
|
||||
UI.layerFacing.checked :
|
||||
UI.layerFill.checked
|
||||
);
|
||||
layers.sparse.setVisible(UI.layerSparse.checked);
|
||||
layers.support.setVisible(UI.layerSupport.checked);
|
||||
}
|
||||
});
|
||||
|
||||
if (currentPrint) {
|
||||
let len = currentPrint.getLayerCount();
|
||||
for (let j = 0; j < len; j++) {
|
||||
currentPrint.showLayer(j, print && j >= lo && j <= hi, moves);
|
||||
currentPrint.showLayer(j, print && j >= lo && j <= hi);
|
||||
}
|
||||
}
|
||||
|
||||
UI.layerPrint.parentNode.style.display = currentPrint && !sla ? '' : 'none';
|
||||
UI.layerMoves.parentNode.style.display = currentPrint && !sla ? '' : 'none';
|
||||
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
|
|
@ -810,7 +771,6 @@
|
|||
SPACE.platform.remove(currentPrint.group);
|
||||
currentPrint = null;
|
||||
}
|
||||
UI.layerPrint.checked = false;
|
||||
}
|
||||
|
||||
function clearSlices() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
class Render {
|
||||
constructor() {
|
||||
this.layers = {};
|
||||
this.profiles = {};
|
||||
}
|
||||
|
||||
setLayer(layer, colors) {
|
||||
|
|
@ -36,12 +37,35 @@
|
|||
return this;
|
||||
}
|
||||
|
||||
addPoly(poly) {
|
||||
this.current.polys.push(poly);
|
||||
addLines(lines, options) {
|
||||
if (options) {
|
||||
for (let i=0; i<lines.length-1; i += 2) {
|
||||
const poly = new BASE.Polygon()
|
||||
.append(lines[i])
|
||||
.append(lines[i+1])
|
||||
.setOpen();
|
||||
this.addPoly(poly, options);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (let i=0; i<lines.length-1; i += 2) {
|
||||
this.addLine(lines[i], lines[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
addPoly(poly, options) {
|
||||
if (options) {
|
||||
this.addPaths([poly], options);
|
||||
} else {
|
||||
this.current.polys.push(poly);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
addPolys(polys) {
|
||||
addPolys(polys, options) {
|
||||
if (options) {
|
||||
return this.addPaths(polys, options);
|
||||
}
|
||||
polys = flat(polys);
|
||||
for (let i=0; i<polys.length; i++) {
|
||||
this.addPoly(polys[i]);
|
||||
|
|
@ -49,6 +73,19 @@
|
|||
return this;
|
||||
}
|
||||
|
||||
addAreas(polys, options) {
|
||||
const faces = this.current.faces;
|
||||
polys = Array.isArray(polys) ? polys : [ polys ];
|
||||
polys.forEach(poly => {
|
||||
poly.earcut().forEach(ep => {
|
||||
ep.forEachPoint(p => { faces.push(p.x, p.y, p.z) });
|
||||
});
|
||||
});
|
||||
if (options && options.outline) {
|
||||
this.addPolys(poly.clone());
|
||||
}
|
||||
}
|
||||
|
||||
addFlats(polys, options) {
|
||||
const opts = options || {};
|
||||
const offset = opts.offset || 1;
|
||||
|
|
@ -81,18 +118,23 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const profile = new THREE.Shape();
|
||||
profile.moveTo(-offset, -offset);
|
||||
profile.lineTo(-offset, offset);
|
||||
profile.lineTo( offset, offset);
|
||||
profile.lineTo( offset, -offset);
|
||||
const profiles = this.profiles;
|
||||
if (!profiles[offset]) {
|
||||
const profile = new THREE.Shape();
|
||||
profile.moveTo(-offset, -offset);
|
||||
profile.lineTo(-offset, offset);
|
||||
profile.lineTo( offset, offset);
|
||||
profile.lineTo( offset, -offset);
|
||||
profiles[offset] = profile;
|
||||
}
|
||||
const profile = profiles[offset].clone();
|
||||
|
||||
polys.forEach(poly => {
|
||||
const contour = [];
|
||||
poly.points.forEach(p => {
|
||||
contour.push(new THREE.Vector2(p.x, p.y));
|
||||
});
|
||||
const {index, faces} = ProfiledContourGeometry(profile, contour, true);
|
||||
const {index, faces} = ProfiledContourGeometry(profile, contour, poly.isClosed());
|
||||
this.current.paths.push({ index, faces, z: poly.getZ() });
|
||||
});
|
||||
return this;
|
||||
|
|
@ -101,9 +143,9 @@
|
|||
|
||||
function flat(polys) {
|
||||
if (Array.isArray(polys)) {
|
||||
return POLY.flatten(polys, [], true);
|
||||
return POLY.flatten(polys.clone(true), [], true);
|
||||
} else {
|
||||
return POLY.flatten([polys], [], true);
|
||||
return POLY.flatten([polys.clone(true)], [], true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1092
src/kiri/slice.js
1092
src/kiri/slice.js
File diff suppressed because it is too large
Load diff
|
|
@ -26,94 +26,13 @@
|
|||
});
|
||||
}
|
||||
|
||||
CAM.renderSlice = function(slice) {
|
||||
let group = new THREE.Group();
|
||||
let render = slice.render;
|
||||
let view = slice.view;
|
||||
|
||||
function addPoly(vertices, poly) {
|
||||
const points = poly.points, len = points.length;
|
||||
for (let i=1; i<len; i++) {
|
||||
const p1 = points[i-1], p2 = points[i];
|
||||
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
}
|
||||
if (!poly.open) {
|
||||
const p1 = points[len - 1], p2 = points[0];
|
||||
vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [layer, data] of Object.entries(render.layers)) {
|
||||
const { polys, lines, faces, paths } = data;
|
||||
if (polys.length || lines.length) {
|
||||
const mat = new THREE.LineBasicMaterial({ color: data.color.line });
|
||||
const geo = new THREE.Geometry(), vert = geo.vertices;
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
addPoly(vert, polys[i]);
|
||||
}
|
||||
for (let i=0, il=lines.length; i<il; i++) {
|
||||
const p = lines[i];
|
||||
vert.push(new THREE.Vector3(p.x, p.y, p.z));
|
||||
}
|
||||
if (vert.length) {
|
||||
group.add(new THREE.LineSegments(geo, mat));
|
||||
}
|
||||
}
|
||||
if (faces.length) {
|
||||
const mat = new THREE.MeshPhongMaterial({
|
||||
transparent: true,
|
||||
shininess: 100,
|
||||
specular: 0x181818,
|
||||
opacity: 1,
|
||||
color: data.color.face,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
|
||||
geo.computeFaceNormals();
|
||||
geo.computeVertexNormals();
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
group.add(mesh);
|
||||
}
|
||||
if (paths.length) {
|
||||
const mat = new THREE.MeshPhongMaterial({
|
||||
transparent: true,
|
||||
shininess: 100,
|
||||
specular: 0x181818,
|
||||
opacity: 1,
|
||||
color: data.color.face,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
paths.forEach((path, i) => {
|
||||
const { index, faces, z } = path;
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
|
||||
geo.setIndex(index);
|
||||
geo.computeFaceNormals();
|
||||
geo.computeVertexNormals();
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.position.z = z;
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
group.add(mesh);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
slice.view.add(group);
|
||||
};
|
||||
|
||||
CAM.sliceRender = function(widget) {
|
||||
return KIRI.Layer.renderSetup().renderSlices(widget.slices);
|
||||
|
||||
let slices = widget.slices;
|
||||
if (!slices) return;
|
||||
|
||||
slices.forEach(function(slice) {
|
||||
if (slice.render) return CAM.renderSlice(slice);
|
||||
|
||||
let tops = slice.tops,
|
||||
layers = slice.layers,
|
||||
outline = layers.outline,
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@
|
|||
sliceDrillReg(settings, sliceAll, zThru);
|
||||
}
|
||||
|
||||
if (procDrill) {
|
||||
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
||||
sliceDrill(drillTool, tslices, sliceAll);
|
||||
}
|
||||
|
||||
// identify through holes
|
||||
thruHoles = tshadow.map(p => p.inner || []).flat();
|
||||
|
||||
|
|
@ -156,7 +151,9 @@
|
|||
slice.z = z;
|
||||
slice.index = sliceIndex++;
|
||||
slice.camMode = PRO.LEVEL;
|
||||
slice.tops[0].traces = POLY.setZ(facing.clone(), slice.z);
|
||||
slice.output()
|
||||
.setLayer("facing", {face: 0, line: 0})
|
||||
.addPolys(POLY.setZ(facing.clone(true), slice.z));
|
||||
sliceAll.push(slice);
|
||||
}
|
||||
}
|
||||
|
|
@ -226,7 +223,9 @@
|
|||
}
|
||||
});
|
||||
|
||||
slice.tops[0].traces = offset;
|
||||
slice.output()
|
||||
.setLayer("roughing", {face: 0, line: 0})
|
||||
.addPolys(offset);
|
||||
slice.index = sliceIndex++;
|
||||
updateOp(index, slices.length);
|
||||
});
|
||||
|
|
@ -299,12 +298,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
slice.tops[0].traces = offset;
|
||||
slice.index = sliceIndex++;
|
||||
|
||||
if (addTabsOutline && slice.z <= zMin + tabHeight) {
|
||||
addCutoutTabs(slice, outlineToolDiam, tabWidth, proc.camTabsCount, proc.camTabsAngle);
|
||||
offset = addCutoutTabs(offset, slice.z, outlineToolDiam, tabWidth, proc.camTabsCount, proc.camTabsAngle);
|
||||
}
|
||||
|
||||
slice.index = sliceIndex++;
|
||||
slice.output()
|
||||
.setLayer("outline", {face: 0, line: 0})
|
||||
.addPolys(offset);
|
||||
});
|
||||
|
||||
sliceAll.appendAll(slices);
|
||||
|
|
@ -337,6 +338,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (procDrill) {
|
||||
maxToolDiam = Math.max(maxToolDiam, drillToolDiam);
|
||||
sliceDrill(drillTool, tslices, sliceAll);
|
||||
}
|
||||
|
||||
// used in printSetup()
|
||||
widget.terrain = terrain;
|
||||
widget.maxToolDiam = maxToolDiam;
|
||||
|
|
@ -347,7 +353,7 @@
|
|||
// drilling op
|
||||
function sliceDrill(tool, slices, output) {
|
||||
let drills = [],
|
||||
drillToolDiam = tool.flueDiameter(),
|
||||
drillToolDiam = tool.fluteDiameter(),
|
||||
centerDiff = drillToolDiam * 0.1,
|
||||
area = (drillToolDiam/2) * (drillToolDiam/2) * Math.PI,
|
||||
areaDelta = area * 0.05;
|
||||
|
|
@ -388,7 +394,9 @@
|
|||
point.y = center.y;
|
||||
});
|
||||
slice.camMode = PRO.DRILL;
|
||||
slice.addTop(null).traces = [ drill ];
|
||||
slice.output()
|
||||
.setLayer("drill", {face: 0, line: 0})
|
||||
.addPolys(drill);
|
||||
output.append(slice);
|
||||
});
|
||||
}
|
||||
|
|
@ -425,21 +433,23 @@
|
|||
.append(point.clone().setZ(bounds.max.z - stock.z - mz)));
|
||||
});
|
||||
slice.camMode = PRO.DRILL;
|
||||
slice.addTop(null).traces = polys;
|
||||
slice.output()
|
||||
.setLayer("register", {face: 0, line: 0})
|
||||
.addPolys(polys);
|
||||
output.append(slice);
|
||||
}
|
||||
}
|
||||
|
||||
// cut outside traces at the right points
|
||||
function addCutoutTabs(slice, toolDiam, tabWidth, tabCount, tabAngle) {
|
||||
function addCutoutTabs(offset, z, toolDiam, tabWidth, tabCount, tabAngle) {
|
||||
// skip if no tops | traces
|
||||
if (slice.tops.length === 0) return;
|
||||
if (offset.length === 0) return offset;
|
||||
|
||||
let notabs = 0;
|
||||
let nutrace = [];
|
||||
|
||||
// find trace with greatest area
|
||||
slice.tops[0].traces.forEach(function(trace, index) {
|
||||
offset.forEach(function(trace, index) {
|
||||
|
||||
// required to match computed order of cutouts
|
||||
trace.setClockwise();
|
||||
|
|
@ -447,7 +457,7 @@
|
|||
let count = tabCount,
|
||||
angle = tabAngle,
|
||||
angle_inc = 360 / count,
|
||||
center = trace.bounds.center(slice.z),
|
||||
center = trace.bounds.center(z),
|
||||
offset = (tabWidth + toolDiam) / 2,
|
||||
ints = [],
|
||||
segs = [];
|
||||
|
|
@ -498,7 +508,7 @@
|
|||
console.log(`unable to compute tabs for ${notabs} traces @ z=${slice.z}`);
|
||||
}
|
||||
|
||||
slice.tops[0].traces = nutrace;
|
||||
return nutrace;
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@
|
|||
slice.lines = newlines = [];
|
||||
newtop = slice.addTop(newPolygon().setOpen()).poly;
|
||||
newtrace = newPolygon().setOpen();
|
||||
sliceout = slice.tops[0].traces = [ ];
|
||||
sliceout = [];
|
||||
for (y = minY; y < maxY; y += resolution) {
|
||||
if (pocketOnly && (data[gridx * stepsy + gridy] || 0) === 0) {
|
||||
end_poly();
|
||||
|
|
@ -224,6 +224,9 @@
|
|||
end_poly();
|
||||
if (sliceout.length > 0) {
|
||||
newslices.push(slice);
|
||||
slice.output()
|
||||
.setLayer("contour x", {face: 0, line: 0})
|
||||
.addPolys(sliceout);
|
||||
}
|
||||
onupdate(++stepsTaken, stepsTotal, "contour x");
|
||||
}
|
||||
|
|
@ -241,7 +244,7 @@
|
|||
slice.lines = newlines = [];
|
||||
newtop = slice.addTop(newPolygon().setOpen()).poly;
|
||||
newtrace = newPolygon().setOpen();
|
||||
sliceout = slice.tops[0].traces = [ ];
|
||||
sliceout = [];
|
||||
for (x = minX; x <= maxX; x += resolution) {
|
||||
if (pocketOnly && (data[gridx * stepsy + gridy] || 0) === 0) {
|
||||
end_poly();
|
||||
|
|
@ -281,6 +284,9 @@
|
|||
end_poly();
|
||||
if (sliceout.length > 0) {
|
||||
newslices.push(slice);
|
||||
slice.output()
|
||||
.setLayer("contour y", {face: 0, line: 0})
|
||||
.addPolys(sliceout);
|
||||
}
|
||||
onupdate(++stepsTaken, stepsTotal, "contour y");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@
|
|||
* @param {number} mode to use to select colors
|
||||
*/
|
||||
FDM.sliceRender = function(widget) {
|
||||
return KIRI.Layer.renderSetup().renderSlices(widget.slices);
|
||||
|
||||
let slices = widget.slices;
|
||||
let settings = widget.settings;
|
||||
let extnum = widget.getExtruder(settings);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,21 @@
|
|||
|
||||
let KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
POLY = BASE.polygons,
|
||||
UTIL = BASE.util,
|
||||
CONF = BASE.config,
|
||||
FDM = KIRI.driver.FDM,
|
||||
SLICER = KIRI.slicer;
|
||||
SLICER = KIRI.slicer,
|
||||
fillArea = POLY.fillArea,
|
||||
newPoint = BASE.newPoint,
|
||||
newSlice = KIRI.newSlice,
|
||||
FILL = KIRI.fill,
|
||||
FILLFIXED = KIRI.fill_fixed,
|
||||
isThin = false;
|
||||
|
||||
function thin(opt) {
|
||||
return isThin ? null : opt;
|
||||
}
|
||||
|
||||
/**
|
||||
* DRIVER SLICE CONTRACT
|
||||
|
|
@ -45,6 +56,8 @@
|
|||
sliceFillAngle = spro.sliceFillAngle,
|
||||
view = widget.mesh && widget.mesh.newGroup ? widget.mesh.newGroup() : null;
|
||||
|
||||
isThin = settings.controller.thinRender;
|
||||
|
||||
if (!(sliceHeight > 0 && sliceHeight < 100)) {
|
||||
return ondone("invalid slice height");
|
||||
}
|
||||
|
|
@ -109,9 +122,6 @@
|
|||
// reset (if necessary) for solids and support projections
|
||||
slices.forEach(function(slice) {
|
||||
slice.extruder = extruder;
|
||||
slice.invalidateFill();
|
||||
slice.invalidateSolids();
|
||||
slice.invalidateSupports();
|
||||
});
|
||||
|
||||
let supportEnabled = spro.sliceSupportEnable && spro.sliceSupportDensity > 0.0,
|
||||
|
|
@ -124,25 +134,27 @@
|
|||
slice.index > slices.length - spro.sliceTopLayers-1 ||
|
||||
spro.sliceFillSparse > 0.95
|
||||
) && !vaseMode;
|
||||
slice.doShells(spro.sliceShells, firstOffset, shellOffset, fillOffset, {
|
||||
doShells(slice, spro.sliceShells, firstOffset, shellOffset, fillOffset, {
|
||||
vase: vaseMode,
|
||||
thin: spro.detectThinWalls
|
||||
});
|
||||
if (solid) slice.doSolidLayerFill(fillSpacing, sliceFillAngle);
|
||||
if (solid) {
|
||||
doSolidLayerFill(slice, fillSpacing, sliceFillAngle);
|
||||
}
|
||||
sliceFillAngle += 90.0;
|
||||
}, "offsets");
|
||||
|
||||
// calculations only relevant when solid layers are used
|
||||
if (doSolidLayers) {
|
||||
forSlices(0.2, 0.34, function(slice) {
|
||||
slice.doDiff(minSolid);
|
||||
if (slice.index > 0) doDiff(slice, minSolid);
|
||||
}, "diff");
|
||||
forSlices(0.34, 0.35, function(slice) {
|
||||
slice.projectFlats(solidLayers);
|
||||
slice.projectBridges(solidLayers);
|
||||
projectFlats(slice, solidLayers);
|
||||
projectBridges(slice, solidLayers);
|
||||
}, "solids");
|
||||
forSlices(0.35, 0.5, function(slice) {
|
||||
slice.doSolidsFill(fillSpacing, sliceFillAngle, minSolid);
|
||||
doSolidsFill(slice, fillSpacing, sliceFillAngle, minSolid);
|
||||
sliceFillAngle += 90.0;
|
||||
}, "solids");
|
||||
}
|
||||
|
|
@ -150,17 +162,17 @@
|
|||
// calculations only relevant when supports are enabled
|
||||
if (supportEnabled) {
|
||||
forSlices(0.5, 0.7, function(slice) {
|
||||
slice.doSupport(spro.sliceSupportOffset, spro.sliceSupportSpan, spro.sliceSupportExtra, supportMinArea, spro.sliceSupportSize, spro.sliceSupportOffset, spro.sliceSupportGap);
|
||||
if (slice.index > 0) doSupport(slice, spro.sliceSupportOffset, spro.sliceSupportSpan, spro.sliceSupportExtra, supportMinArea, spro.sliceSupportSize, spro.sliceSupportOffset, spro.sliceSupportGap);
|
||||
}, "support");
|
||||
forSlices(0.7, 0.8, function(slice) {
|
||||
slice.doSupportFill(nozzleSize, spro.sliceSupportDensity, supportMinArea);
|
||||
doSupportFill(slice, nozzleSize, spro.sliceSupportDensity, supportMinArea);
|
||||
}, "support");
|
||||
}
|
||||
|
||||
// sparse layers only present when non-vase mose and sparse % > 0
|
||||
if (!vaseMode && spro.sliceFillSparse > 0.0) {
|
||||
forSlices(0.8, 1.0, function(slice) {
|
||||
slice.doSparseLayerFill({
|
||||
doSparseLayerFill(slice, {
|
||||
settings: settings,
|
||||
process: spro,
|
||||
device: sdev,
|
||||
|
|
@ -369,4 +381,763 @@
|
|||
return Math.max(min,Math.min(max,v));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute offset shell polygons. For FDM, the first offset is usually half
|
||||
* of the nozzle width. Each subsequent offset is a full nozzle width. User
|
||||
* parameters control tweaks to these numbers to allow for better shell bonding.
|
||||
* The last shell generated is a "fillOffset" shell. Fill lines are clipped to
|
||||
* this polygon. Adjusting fillOffset controls bonding of infill to the shells.
|
||||
*
|
||||
* @param {number} count
|
||||
* @param {number} offset1 first offset
|
||||
* @param {number} offsetN all subsequent offsets
|
||||
* @param {number} fillOffset
|
||||
* @param {Obejct} options
|
||||
*/
|
||||
function doShells(slice, count, offset1, offsetN, fillOffset, options) {
|
||||
const opt = options || {};
|
||||
|
||||
slice.solids = [];
|
||||
|
||||
slice.tops.forEach(function(top) {
|
||||
let top_poly = [ top.poly ];
|
||||
|
||||
if (slice.index === 0) {
|
||||
// console.log({slice_top_0: top_poly, count});
|
||||
// segment polygon
|
||||
}
|
||||
|
||||
if (opt.vase) {
|
||||
// remove top poly inners in vase mode
|
||||
top.poly = top.poly.clone(false);
|
||||
}
|
||||
|
||||
top.shells = [];
|
||||
top.fill_off = [];
|
||||
top.fill_lines = [];
|
||||
|
||||
let last = [],
|
||||
gaps = [],
|
||||
z = top.poly.getZ();
|
||||
|
||||
if (count) {
|
||||
// permit offset of 0 for laser and drag knife
|
||||
if (offset1 === 0 && count === 1) {
|
||||
last = top_poly.clone(true);
|
||||
top.shells = last;
|
||||
} else {
|
||||
if (opt.thin) {
|
||||
top.thin_fill = [];
|
||||
let oso = {z, count, gaps: [], outs: [], minArea: 0.05};
|
||||
POLY.offset(top_poly, [-offset1, -offsetN], oso);
|
||||
|
||||
oso.outs.forEach((polys, i) => {
|
||||
polys.forEach(p => {
|
||||
p.depth = i;
|
||||
if (p.fill_off) {
|
||||
p.fill_off.forEach(pi => pi.depth = i);
|
||||
}
|
||||
top.shells.push(p);
|
||||
});
|
||||
last = polys;
|
||||
});
|
||||
|
||||
// slice.solids.trimmed = slice.solids.trimmed || [];
|
||||
oso.gaps.forEach((polys, i) => {
|
||||
let off = (i == 0 ? offset1 : offsetN);
|
||||
polys = POLY.offset(polys, -off * 0.8, {z, minArea: 0});
|
||||
// polys.forEach(p => { slice.solids.trimmed.push(p); });
|
||||
top.thin_fill.appendAll(cullIntersections(
|
||||
fillArea(polys, 45, off/2, [], 0.01, off*2),
|
||||
fillArea(polys, 135, off/2, [], 0.01, off*2),
|
||||
// fillArea(polys, 90, off, [], 0.05, off*4),
|
||||
// fillArea(polys, 180, off, [], 0.05, off*4),
|
||||
));
|
||||
gaps = polys;
|
||||
});
|
||||
} else {
|
||||
// standard wall offsetting strategy
|
||||
POLY.expand(
|
||||
top_poly, // reference polygon(s)
|
||||
-offset1, // first inset distance
|
||||
z, // set new polys to this z
|
||||
top.shells, // accumulator array
|
||||
count, // number of insets to perform
|
||||
-offsetN, // subsequent inset distance
|
||||
// on each new offset trace ...
|
||||
function(polys, countNow) {
|
||||
last = polys;
|
||||
// mark each poly with depth (offset #) starting at 0
|
||||
polys.forEach(function(p) {
|
||||
p.depth = count - countNow;
|
||||
if (p.fill_off) p.fill_off.forEach(function(pi) {
|
||||
// use negative offset for inners
|
||||
pi.depth = -(count - countNow);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no shells, just infill, is permitted
|
||||
last = [top.poly];
|
||||
}
|
||||
|
||||
// generate fill offset poly set from last offset to top.fill_off
|
||||
if (fillOffset && last.length > 0) {
|
||||
// if gaps present, remove that area from fill inset
|
||||
if (gaps.length) {
|
||||
let nulast = [];
|
||||
POLY.subtract(last, gaps, nulast, null, slice.z);
|
||||
last = nulast;
|
||||
}
|
||||
last.forEach(function(inner) {
|
||||
POLY.offset([inner], -fillOffset, {outs: top.fill_off, flat: true, z: slice.z});
|
||||
});
|
||||
}
|
||||
|
||||
// for diffing
|
||||
top.last = last;
|
||||
|
||||
slice.output()
|
||||
.setLayer('shells', { face: 0x0000aa, line: 0x0000aa })
|
||||
.addPolys(top.shells, thin({ offset: offset1 }))
|
||||
.setLayer('slice', { face: 0, line: 0x888800 })
|
||||
.addPolys(top.poly)
|
||||
.setLayer('offset', { face: 0, line: 0x888888 })
|
||||
.addPolys(top.fill_off)
|
||||
.setLayer('last', { face: 0, line: 0x008888 })
|
||||
.addPolys(top.last)
|
||||
;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an entirely solid layer by filling all top polygons
|
||||
* with an alternating pattern.
|
||||
*
|
||||
* @param {number} linewidth
|
||||
* @param {number} angle
|
||||
* @param {number} density
|
||||
*/
|
||||
function doSolidLayerFill(slice, spacing, angle) {
|
||||
if (slice.tops.length === 0 || typeof(angle) != 'number') {
|
||||
slice.isSolidLayer = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const render = slice.output();
|
||||
|
||||
slice.tops.forEach(function(top) {
|
||||
const lines = fillArea(top.fill_off, angle, spacing, null);
|
||||
top.fill_lines.appendAll(lines);
|
||||
render
|
||||
.setLayer('fill', { face: 0x0000aa, line: 0x0000aa })
|
||||
.addLines(lines, thin({ offset: spacing/2 }));
|
||||
});
|
||||
|
||||
slice.isSolidLayer = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Take output from pluggable sparse infill algorithm and clip to
|
||||
* the bounds of the top polygons and their inner solid areas.
|
||||
*/
|
||||
function doSparseLayerFill(slice, options) {
|
||||
let process = options.process,
|
||||
spacing = options.spacing, // spacing space between fill lines
|
||||
density = options.density, // density of infill 0.0 - 1.0
|
||||
bounds = options.bounds, // bounding box of widget
|
||||
height = options.height, // z layer height
|
||||
type = options.type || 'hex';
|
||||
|
||||
if (slice.tops.length === 0 || density === 0.0 || slice.isSolidLayer) {
|
||||
slice.isSparseFill = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let tops = slice.tops,
|
||||
down = slice.down,
|
||||
clib = self.ClipperLib,
|
||||
ctyp = clib.ClipType,
|
||||
ptyp = clib.PolyType,
|
||||
cfil = clib.PolyFillType,
|
||||
clip = new clib.Clipper(),
|
||||
ctre = new clib.PolyTree(),
|
||||
poly,
|
||||
polys = [],
|
||||
lines = [],
|
||||
line = [],
|
||||
solids = [],
|
||||
// callback passed to pluggable infill algorithm
|
||||
target = {
|
||||
// slice and slice property access
|
||||
slice: function() { return slice },
|
||||
zIndex: function() { return slice.index },
|
||||
zValue: function() { return slice.z },
|
||||
// various option map access
|
||||
options: function() { return options },
|
||||
lineWidth: function() { return options.lineWidth },
|
||||
bounds: function() { return bounds },
|
||||
zHeight: function() { return height },
|
||||
offset: function() { return spacing },
|
||||
density: function() { return density },
|
||||
// output functions
|
||||
emit: function(x,y) {
|
||||
if (isNaN(x)) {
|
||||
solids.push(x);
|
||||
} else {
|
||||
line.push(newPoint(x,y,slice.z));
|
||||
slice.isSparseFill = true;
|
||||
}
|
||||
},
|
||||
newline: function() {
|
||||
if (line.length > 0) {
|
||||
lines.push(line);
|
||||
line = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// use specified fill type
|
||||
if (type && FILL[type]) {
|
||||
FILL[type](target);
|
||||
} else {
|
||||
console.log({missing_infill: type});
|
||||
return;
|
||||
}
|
||||
|
||||
// force emit of last line
|
||||
target.newline();
|
||||
|
||||
// prepare top infill structure
|
||||
tops.forEach(function(top) {
|
||||
top.fill_sparse = [];
|
||||
polys.appendAll(top.fill_off);
|
||||
polys.appendAll(top.solids);
|
||||
});
|
||||
|
||||
function ondone() {
|
||||
tops.forEach(top => {
|
||||
slice.output()
|
||||
.setLayer('infill', { face: 0x0000aa, line: 0x0000aa })
|
||||
.addPolys(top.fill_sparse, thin({ offset: spacing/2 }))
|
||||
});
|
||||
}
|
||||
|
||||
// update fill fingerprint for this slice
|
||||
slice._fill_finger = POLY.fingerprint(polys);
|
||||
|
||||
let skippable = FILLFIXED[type] ? true : false;
|
||||
let miss = false;
|
||||
// if the layer below has the same fingerprint,
|
||||
// we may be able to clone the infill instead of regenerating it
|
||||
if (skippable && slice.fingerprintSame(down)) {
|
||||
// the fill fingerprint can slightly different because of solid projections
|
||||
if (down._fill_finger && POLY.fingerprintCompare(slice._fill_finger, down._fill_finger)) {
|
||||
for (let i=0; i<tops.length; i++) {
|
||||
// the layer below may not have infill computed if it's solid
|
||||
if (down.tops[i].fill_sparse) {
|
||||
tops[i].fill_sparse = down.tops[i].fill_sparse.map(poly => {
|
||||
return poly.clone().setZ(slice.z);
|
||||
});
|
||||
} else {
|
||||
miss = true;
|
||||
}
|
||||
}
|
||||
// if any of the fills as missing from below, re-compute
|
||||
if (!miss) {
|
||||
ondone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sparse_clip = slice.isSparseFill;
|
||||
|
||||
// solid fill areas
|
||||
if (solids.length) {
|
||||
tops.forEach(top => {
|
||||
if (!top.fill_off) return;
|
||||
let masks = top.fill_off.slice();
|
||||
if (top.solids) {
|
||||
masks = POLY.subtract(masks, top.solids, [], null, slice.z);
|
||||
}
|
||||
let angl = process.sliceFillAngle * ((slice.index % 2) + 1);
|
||||
solids.forEach(solid => {
|
||||
let inter = [],
|
||||
fillable = [];
|
||||
masks.forEach(mask => {
|
||||
let p = solid.mask(mask);
|
||||
if (p && p.length) inter.appendAll(p);
|
||||
});
|
||||
// offset fill area to accommodate trace
|
||||
if (inter.length) {
|
||||
POLY.expand(inter, -options.lineWidth/2, slice.z, fillable);
|
||||
}
|
||||
// fill intersected areas
|
||||
if (inter.length) {
|
||||
slice.isSparseFill = true;
|
||||
inter.forEach(p => {
|
||||
p.forEachSegment((p1, p2) => {
|
||||
top.fill_lines.push(p1, p2);
|
||||
});
|
||||
});
|
||||
}
|
||||
if (fillable.length) {
|
||||
let lines = POLY.fillArea(fillable, angl, options.lineWidth);
|
||||
top.fill_lines.appendAll(lines);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// if only solids were added and no lines to clip
|
||||
if (!sparse_clip) {
|
||||
ondone();
|
||||
return;
|
||||
}
|
||||
|
||||
clip.AddPaths(lines, ptyp.ptSubject, false);
|
||||
clip.AddPaths(POLY.toClipper(polys), ptyp.ptClip, true);
|
||||
|
||||
if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
|
||||
ctre.m_AllPolys.forEach(function(node) {
|
||||
poly = POLY.fromClipperNode(node, slice.z);
|
||||
tops.forEach(function(top) {
|
||||
// use only polygons inside this top
|
||||
if (poly.isInside(top.poly)) {
|
||||
top.fill_sparse.push(poly);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ondone();
|
||||
};
|
||||
|
||||
/**
|
||||
* Find difference between fill inset poly on two adjacent layers.
|
||||
* Used to calculate bridges, flats and then solid projections.
|
||||
* 'expand' is used for top offsets in SLA mode
|
||||
*/
|
||||
function doDiff(slice, minArea, sla) {
|
||||
const top = slice,
|
||||
down = slice.down,
|
||||
topInner = sla ? top.topPolys() : top.topInners(),
|
||||
downInner = sla ? down.downPolys() : down.topInners(),
|
||||
bridges = top.bridges = [],
|
||||
flats = down.flats = [];
|
||||
|
||||
// skip diffing layers that are identical
|
||||
if (slice.fingerprintSame(down)) {
|
||||
top.bridges = bridges;
|
||||
down.flats = flats;
|
||||
return;
|
||||
}
|
||||
|
||||
POLY.subtract(topInner, downInner, bridges, flats, slice.z, minArea);
|
||||
|
||||
top.output()
|
||||
.setLayer('bridges', { face: 0x00aaaa, line: 0x00aaaa })
|
||||
.addAreas(bridges)
|
||||
|
||||
down.output()
|
||||
.setLayer('flats', { face: 0xaa00aa, line: 0xaa00aa })
|
||||
.addAreas(flats)
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {Polygon[]} polys
|
||||
*/
|
||||
function addSolidFills(slice, polys) {
|
||||
slice.solids.appendAll(polys);
|
||||
};
|
||||
|
||||
/**
|
||||
* project bottom flats down
|
||||
*/
|
||||
function projectFlats(slice, count) {
|
||||
if (slice.isSolidLayer || !slice.down || !slice.flats) return;
|
||||
projectSolid(slice, slice.flats, count, false, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* project top bridges up
|
||||
*/
|
||||
function projectBridges(slice, count) {
|
||||
if (slice.isSolidLayer || !slice.up || !slice.bridges) return;
|
||||
projectSolid(slice, slice.bridges, count, true, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* fill projected areas and store line data
|
||||
* @return {boolean} true if filled, false if not
|
||||
*/
|
||||
function doSolidsFill(slice, spacing, angle, minArea) {
|
||||
|
||||
const render = slice.output();
|
||||
|
||||
let minarea = minArea || 1,
|
||||
tops = slice.tops,
|
||||
solids = slice.solids,
|
||||
unioned = POLY.union(solids),
|
||||
isSLA = (spacing === undefined && angle === undefined);
|
||||
|
||||
if (solids.length === 0) return false;
|
||||
if (unioned.length === 0) return false;
|
||||
|
||||
let masks,
|
||||
trims = [],
|
||||
inner = isSLA ? slice.topPolys() : slice.topFillOff();
|
||||
|
||||
// trim each solid to the inner bounds
|
||||
unioned.forEach(function(p) {
|
||||
p.setZ(slice.z);
|
||||
inner.forEach(function(i) {
|
||||
if (p.del) return;
|
||||
masks = p.mask(i);
|
||||
if (masks && masks.length > 0) {
|
||||
p.del = true;
|
||||
trims.appendAll(masks);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// clear old solids and make array for new
|
||||
tops.forEach(top => { top.solids = [] });
|
||||
|
||||
// replace solids with merged and trimmed solids
|
||||
slice.solids = solids = trims;
|
||||
|
||||
// parent each solid polygon inside the smallest bounding top
|
||||
solids.forEach(function(solid) {
|
||||
tops.forEach(function(top) {
|
||||
if (top.poly.overlaps(solid)) {
|
||||
if (!solid.parent || solid.parent.area() > top.poly.area()) {
|
||||
if (solid.areaDeep() < minarea) {
|
||||
// console.log({i:slice.index,cull_solid:solid,area:solid.areaDeep()});
|
||||
return;
|
||||
}
|
||||
solid.parent = top.poly;
|
||||
top.solids.push(solid);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// emit solid areas
|
||||
render.setLayer("solids", { face: 0x00dd00 }).addAreas(solids);
|
||||
|
||||
// for SLA to bypass line infill
|
||||
if (isSLA) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// create empty filled line array for each top
|
||||
tops.forEach(function(top) {
|
||||
const tofill = [];
|
||||
const angfill = [];
|
||||
const newfill = [];
|
||||
// determine fill orientation from top
|
||||
solids.forEach(function(solid) {
|
||||
if (solid.parent === top.poly) {
|
||||
if (solid.fillang) {
|
||||
angfill.push(solid);
|
||||
} else {
|
||||
tofill.push(solid);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (tofill.length > 0) {
|
||||
fillArea(tofill, angle, spacing, newfill);
|
||||
top.fill_lines_norm = {angle:angle,spacing:spacing};
|
||||
}
|
||||
if (angfill.length > 0) {
|
||||
top.fill_lines_ang = {spacing:spacing,list:[],poly:[]};
|
||||
angfill.forEach(function(af) {
|
||||
fillArea([af], af.fillang.angle + 45, spacing, newfill);
|
||||
top.fill_lines_ang.list.push(af.fillang.angle + 45);
|
||||
top.fill_lines_ang.poly.push(af.clone());
|
||||
});
|
||||
}
|
||||
|
||||
top.fill_lines.appendAll(newfill);
|
||||
render.setLayer("fill", { line: 0, face: 0 }).addLines(newfill);
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* calculate external overhangs requiring support
|
||||
* this is done bottom-up
|
||||
*
|
||||
* @param {number} minOffset trigger for unsupported distance
|
||||
* @param {number} maxBridge max length before mid supports added
|
||||
* @param {number} expand outer support clip
|
||||
* @param {number} offset inner support clip
|
||||
* @param {number} gap layers between supports and part
|
||||
*/
|
||||
function doSupport(slice, minOffset, maxBridge, expand, minArea, pillarSize, offset, gap) {
|
||||
let min = minArea || 0.01,
|
||||
size = (pillarSize || 1),
|
||||
mergeDist = size * 3, // pillar merge dist
|
||||
tops = slice.topPolys(),
|
||||
trimTo = tops;
|
||||
|
||||
// creates outer clip offset from tops
|
||||
if (expand) {
|
||||
POLY.expand(tops, expand, slice.z, trimTo = []);
|
||||
}
|
||||
|
||||
// create inner clip offset from tops
|
||||
POLY.expand(tops, offset, slice.z, slice.offsets = []);
|
||||
|
||||
let traces = POLY.flatten(slice.topShells().clone(true)),
|
||||
fill = slice.topFill(),
|
||||
points = [],
|
||||
down = slice.down,
|
||||
down_tops = down.topPolys(),
|
||||
down_traces = POLY.flatten(down.topShells().clone(true));
|
||||
|
||||
// check if point is supported by layer below
|
||||
function checkPointSupport(point) {
|
||||
// skip points close to other support points
|
||||
for (let i=0; i<points.length; i++) {
|
||||
if (point.distTo2D(points[i]) < size/4) return;
|
||||
}
|
||||
let supported = point.isInPolygonOnly(down_tops);
|
||||
if (!supported) down_traces.forEach(function(trace) {
|
||||
trace.forEachSegment(function(p1, p2) {
|
||||
if (point.distToLine(p1, p2) <= minOffset) {
|
||||
return supported = true;
|
||||
}
|
||||
});
|
||||
return supported;
|
||||
});
|
||||
if (!supported) points.push(point);
|
||||
}
|
||||
|
||||
// todo support entire line if both endpoints unsupported
|
||||
// segment line and check if midpoints are supported
|
||||
function checkLineSupport(p1, p2, poly) {
|
||||
let dist, i = 1;
|
||||
if ((dist = p1.distTo2D(p2)) >= maxBridge) {
|
||||
let slope = p1.slopeTo(p2).factor(1/dist),
|
||||
segs = Math.floor(dist / maxBridge) + 1,
|
||||
seglen = dist / segs;
|
||||
while (i < segs) {
|
||||
checkPointSupport(p1.projectOnSlope(slope, i++ * seglen));
|
||||
}
|
||||
}
|
||||
if (poly) checkPointSupport(p2);
|
||||
}
|
||||
|
||||
// check trace line support needs
|
||||
traces.forEach(function(trace) {
|
||||
trace.forEachSegment(function(p1, p2) { checkLineSupport(p1, p2, true) });
|
||||
});
|
||||
|
||||
let supports = [];
|
||||
|
||||
// add offset solids to supports (or fill depending)
|
||||
fill.forEachPair(function(p1,p2) { checkLineSupport(p1, p2, false) });
|
||||
// if (top.bridges) POLY.expand(top.bridges, -maxBridge/2, top.z, supports, 1);
|
||||
|
||||
// skip the rest if no points or supports
|
||||
if (!(points.length || supports.length)) return;
|
||||
|
||||
let pillars = [];
|
||||
|
||||
// TODO project points down instead of unioned pillars
|
||||
// TODO merge point/rect into hull of next nearest (up to maxBridge/2 away)
|
||||
// TODO eliminate unions in favor of progress hulling (using previous w/nearness)
|
||||
// TODO align pillar diamond along line (when doing line checks)
|
||||
|
||||
// for each point, create a bounding rectangle
|
||||
points.forEach(function(point) {
|
||||
pillars.push(BASE.newPolygon().centerRectangle(point, size/2, size/2));
|
||||
});
|
||||
|
||||
// merge pillars and replace with convex hull of outer points (aka smoothing)
|
||||
pillars = POLY.union(pillars).forEach(function(pillar) {
|
||||
supports.push(BASE.newPolygon().createConvexHull(pillar.points));
|
||||
});
|
||||
|
||||
// return top.supports = supports;
|
||||
// then union supports
|
||||
supports = POLY.union(supports);
|
||||
|
||||
// constrain support poly to top polys
|
||||
supports = POLY.trimTo(supports, trimTo);
|
||||
|
||||
let depth = 0;
|
||||
while (down && supports.length > 0) {
|
||||
down.supports = down.supports || [];
|
||||
|
||||
let trimmed = [],
|
||||
culled = [];
|
||||
|
||||
// clip supports to shell offsets
|
||||
POLY.subtract(supports, down.topPolys(), trimmed, null, slice.z, min);
|
||||
|
||||
// set depth hint on support polys for infill density
|
||||
trimmed.forEach(function(trim) {
|
||||
if (trim.area() < 0.1) return;
|
||||
culled.push(trim.setZ(down.z));
|
||||
});
|
||||
|
||||
// exit when no more support polys exist
|
||||
if (culled.length === 0) break;
|
||||
|
||||
// new bridge polys for next pass (skip first layer below)
|
||||
if (depth >= gap) {
|
||||
down.supports.appendAll(culled);
|
||||
// down.output()
|
||||
// .setLayer("support", { line: 0xff0000 })
|
||||
// .addPolys(culled, thin({ offset: 0.4 }))
|
||||
}
|
||||
|
||||
supports = culled;
|
||||
down = down.down;
|
||||
depth++;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} linewidth
|
||||
* @param {number} angle
|
||||
* @param {number} density
|
||||
* @param {number} offset
|
||||
*/
|
||||
function doSupportFill(slice, linewidth, density, minArea) {
|
||||
let supports = slice.supports,
|
||||
nsB = [],
|
||||
nsC = [],
|
||||
min = minArea || 0.1;
|
||||
|
||||
// create support clip offset
|
||||
// POLY.expand(slice.gatherTopPolys([]), offset, slice.z, slice.offsets = []);
|
||||
|
||||
if (!supports) return;
|
||||
|
||||
// union supports
|
||||
supports = POLY.union(supports);
|
||||
|
||||
// trim to clip offsets
|
||||
if (slice.offsets) POLY.subtract(supports, slice.offsets, nsB, null, slice.z, min);
|
||||
supports = nsB;
|
||||
|
||||
// also trim to lower offsets, if they exist
|
||||
if (slice.down && slice.down.offsets) {
|
||||
POLY.subtract(nsB, slice.down.offsets, nsC, null, slice.z, min);
|
||||
supports = nsC;
|
||||
}
|
||||
|
||||
if (supports) supports.forEach(function (poly) {
|
||||
// angle based on width/height ratio
|
||||
let angle = (poly.bounds.width() / poly.bounds.height() > 1) ? 90 : 0;
|
||||
// calculate fill density
|
||||
let spacing = linewidth * (1 / density);
|
||||
// inset support poly for fill lines 33% of nozzle width
|
||||
let inset = POLY.offset([poly], -linewidth/3, {flat: true, z: slice.z});
|
||||
// do the fill
|
||||
if (inset.length > 0) {
|
||||
fillArea(inset, angle, spacing, poly.fills = []);
|
||||
if (poly.fills.length);
|
||||
slice.output()
|
||||
.setLayer("support", { line: 0xff0000 })
|
||||
.addLines(poly.fills, thin({ offset: 0.4 }))
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// re-assign new supports back to slice
|
||||
slice.supports = supports;
|
||||
|
||||
slice.output()
|
||||
.setLayer("support", { line: 0xff0000 })
|
||||
.addPolys(supports, thin({ offset: 0.4 }))
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Slice} slice
|
||||
* @param {Polygon[]} polys
|
||||
* @param {number} count
|
||||
* @param {boolean} up
|
||||
* @param {boolean} first
|
||||
* @returns {*}
|
||||
*/
|
||||
function projectSolid(slice, polys, count, up, first) {
|
||||
if (!slice || slice.isSolidLayer || count <= 0) {
|
||||
return;
|
||||
}
|
||||
let clones = polys.clone(true);
|
||||
if (first) {
|
||||
clones.forEach(function(p) {
|
||||
p.hintFillAngle();
|
||||
});
|
||||
}
|
||||
addSolidFills(slice, clones);
|
||||
if (count > 0) {
|
||||
if (up) projectSolid(slice.up, polys, count-1, true, false);
|
||||
else projectSolid(slice.down, polys, count-1, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* given an array of arrays of points (lines), eliminate intersections
|
||||
* between groups, then return a unified array of shortest non-intersects.
|
||||
*
|
||||
* @returns {Point[]}
|
||||
*/
|
||||
function cullIntersections() {
|
||||
function toLines(pts) {
|
||||
let lns = [];
|
||||
for (let i=0, il=pts.length; i<il; i += 2) {
|
||||
lns.push({a: pts[i], b: pts[i+1], l: pts[i].distTo2D(pts[i+1])});
|
||||
}
|
||||
return lns;
|
||||
}
|
||||
let aOa = [...arguments].filter(t => t);
|
||||
if (aOa.length < 1) return;
|
||||
let aa = toLines(aOa.shift());
|
||||
while (aOa.length) {
|
||||
let bb = toLines(aOa.shift());
|
||||
loop: for (let i=0, il=aa.length; i<il; i++) {
|
||||
let al = aa[i];
|
||||
if (al.del) {
|
||||
continue;
|
||||
}
|
||||
for (let j=0, jl=bb.length; j<jl; j++) {
|
||||
let bl = bb[j];
|
||||
if (bl.del) {
|
||||
continue;
|
||||
}
|
||||
if (UTIL.intersect(al.a, al.b, bl.a, bl.b, BASE.key.SEGINT)) {
|
||||
if (al.l < bl.l) {
|
||||
bl.del = true;
|
||||
} else {
|
||||
al.del = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
aa = aa.filter(l => !l.del).concat(bb.filter(l => !l.del));
|
||||
}
|
||||
let good = [];
|
||||
for (let i=0, il=aa.length; i<il; i++) {
|
||||
let al = aa[i];
|
||||
good.push(al.a);
|
||||
good.push(al.b);
|
||||
}
|
||||
return good.length > 2 ? good : [];
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue