add optional alternating/zigzag for area linear surfacing
This commit is contained in:
parent
c279e1140b
commit
c46f88b412
5 changed files with 66 additions and 20 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { base } from './base.js';
|
||||
import { newPoint } from './point.js';
|
||||
import { newPolygon } from './polygon.js';
|
||||
|
||||
/**
|
||||
* Emit each element in array based on next closest endpoint
|
||||
|
|
@ -18,23 +19,26 @@ export function tip2tipEmit(array, startPoint, emitter) {
|
|||
for (;;) {
|
||||
found = null;
|
||||
mindist = Infinity;
|
||||
array.forEach(function (el) {
|
||||
if (el.delete) return;
|
||||
for (let el of array) {
|
||||
if (el.delete) continue;
|
||||
dist = startPoint.distTo2D(el.first);
|
||||
if (dist < mindist) {
|
||||
found = { el: el, first: el.first, last: el.last };
|
||||
mindist = dist;
|
||||
}
|
||||
// when heads is set, it's first to first
|
||||
// not first/last to closest first/last
|
||||
if (el.heads) continue;
|
||||
dist = startPoint.distTo2D(el.last);
|
||||
if (dist < mindist) {
|
||||
found = { el: el, first: el.last, last: el.first };
|
||||
mindist = dist;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (found) {
|
||||
found.el.delete = true;
|
||||
startPoint = found.last;
|
||||
emitter(found.el, found.first, ++count);
|
||||
emitter(found.el, found.first, ++count, mindist);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
|
@ -42,6 +46,38 @@ export function tip2tipEmit(array, startPoint, emitter) {
|
|||
return startPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit each element in array based on next closest endpoint
|
||||
* Uses greedy nearest-neighbor algorithm to minimize travel distance
|
||||
* When polygons are close, a new polygon is injected that joins them
|
||||
* and the next polygon is reversed so that tips would merge on output
|
||||
* @param {Array} paths - Array of elements with { first, last } Point properties
|
||||
* @param {Point} startPoint - Starting point for path ordering
|
||||
* @param {Number} mergeDist - threshold for merging Polygons
|
||||
* @returns {Array} reduced merged Polygon array
|
||||
*/
|
||||
export function tip2tipJoin(paths, startPoint, mergeDist) {
|
||||
let newp = [];
|
||||
tip2tipEmit(paths.map(path => ({
|
||||
path,
|
||||
last: path.last(),
|
||||
first: path.first()
|
||||
})), startPoint, (next, point, count, dist) => {
|
||||
if (next.last === point) {
|
||||
next.path.reverse();
|
||||
}
|
||||
if (newp.length && dist < mergeDist) {
|
||||
let lastpath = newp.peek();
|
||||
newp.push(newPolygon().addPoints([
|
||||
lastpath.last(),
|
||||
next.path.first()
|
||||
]));
|
||||
}
|
||||
newp.push(next.path);
|
||||
});
|
||||
return newp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit polygons in optimal order based on nearest point
|
||||
* Like tip2tipEmit but accepts polygons where next closest point can be anywhere
|
||||
|
|
|
|||
|
|
@ -446,6 +446,7 @@ export const conf = {
|
|||
camAreaDogbones: false,
|
||||
camAreaRevbones: false,
|
||||
camAreaOutline: false,
|
||||
camAreaZigZag: true,
|
||||
camContourAngle: 85,
|
||||
camContourBottom: false,
|
||||
camContourBridge: 0,
|
||||
|
|
|
|||
|
|
@ -678,6 +678,7 @@ export function createPopOps() {
|
|||
tr_type: 'camAreaTrace',
|
||||
sr_type: 'camAreaSurface',
|
||||
sr_angle: 'camAreaAngle',
|
||||
sr_alter: 'camAreaZigZag',
|
||||
over: 'camAreaOver',
|
||||
down: 'camAreaDown',
|
||||
rate: 'camAreaSpeed',
|
||||
|
|
@ -713,6 +714,7 @@ export function createPopOps() {
|
|||
over: UC.newInput(LANG.cc_sovr_s, { title: LANG.cc_sovr_l, convert: toFloat, bound: UC.bound(0.001, 100.0), show: () => isClear() || isSurface() }),
|
||||
down: UC.newInput(LANG.cc_sdwn_s, { title: LANG.cc_sdwn_l, convert: toFloat, bound: UC.bound(0, 100.0), units, show: () => isClear() || isTrace() }),
|
||||
refine: UC.newInput(LANG.cp_refi_s, { title: LANG.cp_refi_l, convert: toInt, show: isSurface }),
|
||||
sr_alter: UC.newBoolean("alternate", undefined, { title: "alternate cutting direction", show: isSurfaceLinear }),
|
||||
dogbones: UC.newBoolean(LANG.co_dogb_s, undefined, { title: LANG.co_dogb_l, show: isTrace }),
|
||||
revbones: UC.newBoolean(LANG.co_dogr_s, undefined, { title: LANG.co_dogr_l, show: () => env.poppedRec.dogbones }),
|
||||
exp_end: UC.endExpand(),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { newPoint } from '../../../geo/point.js';
|
|||
import { newPolygon } from '../../../geo/polygon.js';
|
||||
import { polygons as POLY } from '../../../geo/polygons.js';
|
||||
import { util as base_util } from '../../../geo/base.js';
|
||||
import { tip2tipJoin } from '../../../geo/paths.js';
|
||||
import { CAM } from './driver-be.js';
|
||||
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
|
@ -41,6 +42,11 @@ class OpArea extends CamOp {
|
|||
let thruHoles = state.shadow.holes;
|
||||
let roundSharps = settings.process.camRoundCorners;
|
||||
|
||||
// get these once and re-use for each area
|
||||
let vertices = mode === 'surface' ?
|
||||
widget.getGeoVertices({ unroll: true, translate: true }) :
|
||||
undefined;
|
||||
|
||||
// also updates tab offsets
|
||||
setToolDiam(toolDiam);
|
||||
|
||||
|
|
@ -302,7 +308,7 @@ class OpArea extends CamOp {
|
|||
progress(proc, 'trace');
|
||||
} else
|
||||
if (mode === 'surface') {
|
||||
let { sr_type, sr_angle, tolerance } = op;
|
||||
let { sr_type, sr_angle, sr_alter, tolerance } = op;
|
||||
|
||||
let resolution = tolerance || 0.05;
|
||||
let raster = await self.get_raster_gpu({ mode: "tracing", resolution });
|
||||
|
|
@ -327,8 +333,14 @@ class OpArea extends CamOp {
|
|||
paths.push(POLY.fromClipperNode(node, 0));
|
||||
}
|
||||
}
|
||||
// convert resulting poly lines to raster float32 array groups
|
||||
paths = paths.map(poly => poly.points.map(p => [ p.x, p.y ]).flat().toFloat32());
|
||||
// in surfacing mode, direction is simply reversal
|
||||
if (direction === 'climb') {
|
||||
paths.forEach(path => path.reverse());
|
||||
}
|
||||
// optional alternating paths
|
||||
if (sr_alter) {
|
||||
paths = tip2tipJoin(paths, paths[0].first(), toolDiam * 4);
|
||||
}
|
||||
} else
|
||||
if (sr_type === 'offset') {
|
||||
// progressive inset from perimeter
|
||||
|
|
@ -336,9 +348,12 @@ class OpArea extends CamOp {
|
|||
count: 999, outs: paths, flat: true, z: 0, minArea: 0
|
||||
});
|
||||
paths.forEach(poly => poly.isClosed() && poly.push(poly.first()));
|
||||
paths = paths.map(poly => poly.points.map(p => [ p.x, p.y ]).flat().toFloat32());
|
||||
POLY.setWinding(paths.filter(p => p.isClosed()), direction === 'climb');
|
||||
}
|
||||
|
||||
// convert resulting poly lines to raster float32 array groups
|
||||
paths = paths.map(poly => poly.points.map(p => [ p.x, p.y ]).flat().toFloat32());
|
||||
|
||||
// prepare tool mesh points
|
||||
let toolBounds = new THREE.Box3()
|
||||
.expandByPoint({ x: -toolDiam/2, y: -toolDiam/2, z: 0 })
|
||||
|
|
@ -350,7 +365,6 @@ class OpArea extends CamOp {
|
|||
let toolData = { positions: toolPos, bounds: toolBounds };
|
||||
|
||||
// prepare terrain and raster paths over terrain
|
||||
let vertices = widget.getGeoVertices({ unroll: true, translate: true });
|
||||
let wbounds = bounds.clone().expandByVector({ x: toolDiam/2, y: toolDiam/2, z: 0 });
|
||||
wbounds.min.z = zBottom;
|
||||
wbounds.max.z = zTop;
|
||||
|
|
@ -408,17 +422,11 @@ class OpArea extends CamOp {
|
|||
if (surfaces.length) {
|
||||
setContouring(true);
|
||||
for (let surface of surfaces) {
|
||||
let array = surface.map(poly => { return {
|
||||
el: poly,
|
||||
first: poly.first(),
|
||||
last: poly.last()
|
||||
} });
|
||||
tip2tipEmit(array, printPoint, (next, point) => {
|
||||
for (let poly of surface) {
|
||||
setNextIsMove();
|
||||
if (next.last === point) next.el.reverse();
|
||||
printPoint = polyEmit(next.el);
|
||||
printPoint = polyEmit(poly);
|
||||
newLayer();
|
||||
});
|
||||
}
|
||||
}
|
||||
setContouring(false);
|
||||
// skip areas when processing surfaces
|
||||
|
|
|
|||
|
|
@ -122,10 +122,9 @@ class OpContour extends CamOp {
|
|||
let { process } = settings;
|
||||
|
||||
let { polyEmit, setContouring, setTolerance, setTool } = ops;
|
||||
let { widget, camOut, newLayer, zmax } = ops;
|
||||
let { widget, newLayer, zmax } = ops;
|
||||
|
||||
let bounds = widget.getBoundingBox();
|
||||
let toolDiam = this.toolDiam;
|
||||
let depthData = [];
|
||||
|
||||
setTool(op.tool, op.rate, process.camFastFeedZ);
|
||||
|
|
|
|||
Loading…
Reference in a new issue