add curves-only support to gpu contour, merge co-planar point runs

This commit is contained in:
Stewart Allen 2025-11-11 15:06:05 -05:00
commit f4d7a37265
7 changed files with 84 additions and 39 deletions

View file

@ -127,6 +127,12 @@ class Point {
return p;
}
// annotate instance with other field data (see clone())
annotate(obj = {}) {
Object.assign(this, obj);
return this;
}
slopeTo(p) {
return newSlope(this, p);
}

View file

@ -908,6 +908,7 @@ export class Polygon {
* todo make more efficient
*
* @param {Boolean} deep
* @param {String[]} fields to copy beyond class fields
* @returns {Polygon}
*/
clone(deep, fields) {
@ -2145,6 +2146,7 @@ export class Polygon {
return null;
}
// annotate instance with other field data (see clone())
annotate(obj = {}) {
Object.assign(this, obj);
return this;

View file

@ -12,10 +12,8 @@ export function cam_export(print, online) {
const widget = print.widgets[0];
if (!widget) return;
console.log({ cam_export: print });
const { settings } = print;
const { device, tools } = settings;
const { device } = settings;
let i,
time = 0,
@ -169,7 +167,6 @@ export function cam_export(print, online) {
function moveTo(out, opt = {}) {
let laser = out.type === 'laser';
let newpos = out.point;
console.log(mode.type, out.point);
// no point == dwell
// out.speed = time to dwell in ms

View file

@ -113,11 +113,11 @@ class OpContour extends CamOp {
prepare(ops, progress) {
let { op, state, sliceOut } = this;
let { settings, widget } = state;
let { settings } = state;
let { process } = settings;
let { setTolerance, setTool, setSpindle, setPrintPoint } = ops;
let { camOut, polyEmit, newLayer, printPoint, lastPoint } = ops;
let { camOut, newLayer, printPoint, lastPoint } = ops;
let { bounds, zmax } = ops;
let toolDiam = this.toolDiam;
@ -136,9 +136,9 @@ class OpContour extends CamOp {
if (!slice.camLines) {
continue;
}
let polys = [], poly, emit;
let polys = [], poly;
slice.camLines.forEach(function (poly) {
if (depthFirst) poly = poly.clone(true);
if (depthFirst) poly = poly.clone(true).annotate({ slice: slice.index + 1 });
polys.push({ first: poly.first(), last: poly.last(), poly: poly });
});
if (depthFirst) {
@ -150,7 +150,7 @@ class OpContour extends CamOp {
poly.reverse();
}
poly.forEachPoint(function (point, pidx) {
camOut(point.clone(), pidx > 0, stepover);
camOut(point.clone(), pidx > 0, { moveLen: stepover });
}, false);
});
newLayer();
@ -164,7 +164,7 @@ class OpContour extends CamOp {
poly.reverse();
}
poly.forEachPoint(function (point, pidx) {
camOut(point.clone(), pidx > 0, stepover);
camOut(point.clone().annotate({ slice: poly.slice }), pidx > 0, { moveLen: stepover });
}, false);
newLayer();
return lastPoint();

View file

@ -64,7 +64,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
stockz = stock.z * (isIndexed ? 0.5 : 1),
outer = settings.bounds || widget.getPositionBox(),
outerz = outer.max.z,
// slices = widget.slices,
zclear = (process.camZClearance || 1),
zmax_force = process.camForceZMax || false,
zmax_outer = stockz + zclear,
@ -81,7 +80,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
originx = (startCenter ? 0 : -stock.x / 2) + (process.camOriginOffX || 0),
originy = (startCenter ? 0 : -stock.y / 2) + (process.camOriginOffY || 0),
origin = newPoint(originx + wmx, originy + wmy, zmax),
// output = print.output,
easeDown = process.camEaseDown,
easeAngle = process.camEaseAngle,
depthFirst = process.camDepthFirst,
@ -99,7 +97,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
newOutput = print.output || [],
layerOut = [],
printPoint,
// isNewMode,
isPocket,
isContour,
isRough,
@ -263,8 +260,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
*/
function layerPush(point, emit, speed, tool, options) {
const { type, center, arcPoints } = options ?? {};
const dz = (point && lastPush && lastPush.point) ? point.z - lastPush.point.z : 0;
if (dz < 0 && speed > plungeRate) {
const dz = (point && lastPush?.point) ? point.z - lastPush.point.z : 0;
if (dz < -0.05 && speed > plungeRate) {
speed = plungeRate;
}
// if (options?.type !== 'lerp') console.log( point, currentOp.type );
@ -320,7 +317,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
p.x + wmx,
p.y + wmy,
p.z + zadd
).setA(p.a ?? lastPoint?.a);
).setA(p.a ?? lastPoint?.a).annotate({ slice: p.slice });
}
/**
@ -359,14 +356,12 @@ export function prepEach(widget, settings, print, firstPoint, update) {
factor = 1,
} = opts ?? {}
// console.log({radius})
const isArc = emit == 2 || emit == 3;
const pointA = point.a;
// apply widget movement pos
const pointA = point.a;
point = applyWidgetMovement(point);
// console.log(point.z);
if (nextIsMove) {
emit = 0;
nextIsMove = false;
@ -429,6 +424,9 @@ export function prepEach(widget, settings, print, firstPoint, update) {
return;
}
// no jump moves in contour mode to adjacent slice points
let steady = lastPoint && currentOp.type === 'contour' && Math.abs(point.slice - lastPoint.slice) < 4;
// convert short planar moves to cuts in some cases
if (hasDelta && !isArc && isMove && deltaXY <= moveLen && deltaZ <= 0 && !lasering) {
let iscontour = tolerance > 0;
@ -438,7 +436,6 @@ export function prepEach(widget, settings, print, firstPoint, update) {
emit = 1;
isMove = false;
} else if (deltaZ <= -tolerance) {
console.log({ deltaZ, tolerance, iscontour });
// move over before descending
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
// new pos for plunge calc
@ -450,7 +447,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
} else if (point.z < lastPoint.z) {
layerPush(point.clone().setZ(lastPoint.z), 0, 0, tool);
}
} else if (isMove) {
} else if (isMove && !steady) {
// for longer moves, check the terrain to see if we need to go up and over
const bigXY = (deltaXY > moveLen && !lasering);
const bigZ = (deltaZ > toolDiam / 2 && deltaXY > tolerance);
@ -497,13 +494,15 @@ export function prepEach(widget, settings, print, firstPoint, update) {
}
// set new plunge rate
let tmprate;
if (false)
if (!lasering && !isLathe && deltaZ < -tolerance) {
let threshold = Math.min(deltaXY / 2, absDeltaZ),
modifier = threshold / absDeltaZ;
if (synthPlunge && threshold && modifier && deltaXY > tolerance) {
// use modifier to speed up long XY move plunge rates
// console.log('modifier', modifier);
rate = Math.max(
tmprate = Math.max(
plungeRate,
Math.round(plungeRate + ((feedRate - plungeRate) * modifier))
);
@ -511,7 +510,8 @@ export function prepEach(widget, settings, print, firstPoint, update) {
let L = Math.hypot(deltaXY, absDeltaZ);
let limXY = feedRate * L / deltaXY;
let limZ = plungeRate * L / Math.abs(deltaZ);
rate = Math.min(feedRate, limXY, limZ);
tmprate = Math.min(feedRate, limXY, limZ);
console.log({ rate_override: rate, was: rate });
// let zps = len / absDeltaZ;
// rate = Math.max(
// plungeRate,
@ -527,7 +527,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
lastPoint = layerPush(
point,
clockwise ? 2 : 3,
rate,
tmprate ?? rate,
tool,
{
center,
@ -542,7 +542,7 @@ export function prepEach(widget, settings, print, firstPoint, update) {
lastPoint = layerPush(
point,
emit,
rate,
tmprate ?? rate,
tool
);
}

View file

@ -7,6 +7,7 @@ import { newSlice } from '../../core/slice.js';
import { Slicer as topo_slicer } from './slicer_topo.js';
import { polygons as POLY } from '../../../geo/polygons.js';
import { Tool } from './tool.js';
import { poly2polyEmit } from '../../../geo/paths.js';
const RAD2DEG = 180 / Math.PI;
const DEG2RAD = Math.PI / 180;
@ -42,7 +43,7 @@ export class Topo {
tool = new Tool(settings, contour.tool),
toolOffset = tool.generateProfile(resolution).profile,
toolDiameter = tool.fluteDiameter(),
toolStep = toolDiameter * contour.step, //tool.contourOffset(contour.step),
toolStep = toolDiameter * contour.step,
leave = contour.leave || 0,
maxangle = contour.angle,
curvesOnly = contour.curves,
@ -75,12 +76,7 @@ export class Topo {
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;
debug_clips = false;
if (tolerance === 0 && !topoCache) {
console.log(widget.id, 'topo auto tolerance', resolution.round(4));
@ -142,7 +138,7 @@ export class Topo {
});
let xStep = density;
let yStep = Math.ceil(toolStep / resolution);
let epsilon = 10e-5;
let epsilon = 10e-4;
await gpu.loadTool({
sparseData: toolData
});
@ -180,7 +176,7 @@ export class Topo {
;
let slice = newSlice(i);
let poly = newPolygon().setOpen();
let lines = slice.camLines = [ ];
let lines = [ ];
let skip = 0;
let lp;
for (let p of points) {
@ -216,9 +212,11 @@ export class Topo {
poly.push(lp = p);
skip = 0;
}
// require two points or more
if (poly.length > 1) {
lines.push(poly);
}
// raize output points when inside tab boundaries
if (!inside && tabsOn && clipTab.length)
for (let poly of lines) {
for (let p of poly.points) {
@ -230,6 +228,49 @@ export class Topo {
}
}
}
// drop interior points along a continuous slope
for (let poly of lines) {
let { points } = poly;
let merged = [ points[0] ];
let lp = points[1];
let dz = points[0].z - lp.z;
for (let i=2; i<points.length; i++) {
let np = points[i];
let ndz = np.z - lp.z;
if (Math.abs(dz - ndz) > epsilon) {
// slope changed
merged.push(lp);
dz = ndz;
} else if (curvesOnly && Math.abs(ndz) < epsilon) {
// add empty points as path separators
merged.push(undefined);
}
lp = np;
}
if (merged.peek() !== lp) {
merged.push(lp);
}
poly.points = merged;
}
// drop flat regions when enabled
if (curvesOnly) {
let nulines = [];
for (let poly of lines) {
let { points } = poly;
let newp = [];
for (let p of points) {
if (p) {
newp.push(p);
} else if (newp.length) {
nulines.push(newPolygon(newp).setOpen());
newp = [];
}
}
nulines.push(newPolygon(newp).setOpen());
}
lines = nulines.filter(p => p.perimeter() > toolStep);
}
slice.camLines = lines;
slices.push(slice);
if (inside) {
onupdate(i, numScanlines);
@ -659,9 +700,9 @@ export class Trace {
console.log(...arguments);
}
const push_point = this.push_point = function (x, y, z) {
this.push_point = function (x, y, z) {
const newP = newPoint(x, y, z);
const lastP = lastPP;//trace.last();
const lastP = lastPP;
if (lastP) {
const dl = (x - lastP.x) || (y - lastP.y);
@ -678,7 +719,6 @@ export class Trace {
}
if (curvesOnly) {
const dv = contourX ? Math.abs(lastP.x - x) : Math.abs(lastP.y - y);
// const dz = lastPP.z - z;
const angle = Math.atan2(Math.abs(dz), dv) * RAD2DEG;
if (angle > maxangle) {
end_poly();

View file

@ -321,10 +321,10 @@ self.lang['en-us'] = {
pt_heal_l: ["attempt to heal non-manifold meshes during slicing. may increase slicing time. enables a warning when non-manifold geometries are detected."],
pt_thrd_s: "threaded",
pt_thrd_l: ["use parallel processing when browser supports nested workers. may increase memory pressure on larger models. disable if you are getting out of memory errors"],
pt_assy_s: "assembly",
pt_assy_s: "web asm",
pt_assy_l: ["use web assembly when supported by browser. if you experience artifacts or memory errors, try disabling this."],
pt_wgpu_s: "web gpu",
pt_wgpu_l: ["use web gpu when supported by browser"],
pt_wgpu_l: ["use web gpu when supported by browser. if you experience artifacts or memory errors, try disabling this."],
xp_menu: "exports",