update single width extrusion claim system. does not require splitting medial axis segments

This commit is contained in:
Stewart Allen 2025-09-21 18:17:56 -04:00
commit 6a44654714
3 changed files with 29 additions and 39 deletions

View file

@ -114,11 +114,16 @@ class Point {
return this.key; return this.key;
} }
clone() { clone(keys) {
let p = newPoint(this.x, this.y, this.z, this._key); let p = newPoint(this.x, this.y, this.z, this._key);
if (this.a !== undefined) { if (this.a !== undefined) {
p.a = this.a; p.a = this.a;
} }
if (keys) {
for (let key of keys) {
p[key] = this[key];
}
}
return p; return p;
} }

View file

@ -2322,7 +2322,7 @@ export class Polygon {
// split long straight lines into segments no longer than max // split long straight lines into segments no longer than max
// and return a new polygon // and return a new polygon
segment(max = 1) { segment(max = 1, mark) {
const newp = []; const newp = [];
const points = this.points; const points = this.points;
const length = points.length; const length = points.length;
@ -2334,6 +2334,7 @@ export class Polygon {
const dy = p2.y - p1.y; const dy = p2.y - p1.y;
const dl = Math.sqrt(dx * dx + dy * dy); const dl = Math.sqrt(dx * dx + dy * dy);
newp.push(p1); newp.push(p1);
if (mark) p1.segment = p1;
if (dl < max) { if (dl < max) {
continue; continue;
} }
@ -2348,10 +2349,12 @@ export class Polygon {
newp.push(newPoint(ox, oy, (p1.z + p2.z) / 2)); newp.push(newPoint(ox, oy, (p1.z + p2.z) / 2));
ox += ix; ox += ix;
oy += iy; oy += iy;
// mark new point with first point of originating segment
if (mark) newp.peek().segment = p1;
} }
} }
if (newp.length > length) { if (newp.length > length) {
return newPolygon().addPoints(newp.map(p => p.clone())).setOpenValue(this.open); return newPolygon().addPoints(newp.map(p => p.clone(['segment']))).setOpenValue(this.open);
} }
return this; return this;
} }

View file

@ -218,11 +218,14 @@ function thin_type_3(params) {
// console.log({ lines, polys, trace }); // console.log({ lines, polys, trace });
// show extrusion // show extrusion
let zo = z + 0.1;
if (debugExtrusion) if (debugExtrusion)
for (let trace of traces)
for (let point of trace) { for (let point of trace) {
polys.push(newPolygon().centerCircle(point, point.r, 12).setZ(z)); polys.push(newPolygon().centerCircle(point, point.r, 12).setZ(zo = zo + 0.005));
} }
if (!debugExtrusion)
POLY.setZ([...lines, ...polys], z); POLY.setZ([...lines, ...polys], z);
if (lines.length) top.thin_fill = lines; if (lines.length) top.thin_fill = lines;
@ -279,7 +282,11 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
}); });
} }
// project from first inset and find greated offset intersection // project from first inset and find greated offset intersection.
// this examples show how to brute force create a medial axis from
// micro stepping insets. the same intersect technique used with
// medial axis can be applied to this structure to produce an
// equivalent output. not as computationally efficient, but much simpler.
if (brute) { if (brute) {
let { intersectRayLine } = base.util; let { intersectRayLine } = base.util;
let tests = insets.slice(1); let tests = insets.slice(1);
@ -375,8 +382,6 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
return rec; return rec;
} }
let nusegs = [];
// for each standalone noodle, construct medial axis // for each standalone noodle, construct medial axis
// map medial axis points into de-duplicated segments // map medial axis points into de-duplicated segments
for (let poly of noodle) { for (let poly of noodle) {
@ -393,31 +398,6 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
} }
} }
// break down long segments
for (let seg of lineMap.values()) {
let { p0, p1 } = seg;
let len = seg.len = pointDist(p0, p1);
// add to nusegs
if (len <= minR) {
nusegs.push({ p0, p1 });
if (showMedialAxis) {
lines.push(new Point(p0.x, p0.y), new Point(p1.x, p1.y));
}
} else {
let steps = Math.ceil(len / minR) + 1;
let dx = (p1.x - p0.x) / steps;
let dy = (p1.y - p0.y) / steps;
for (let i=0; i<steps; i++) {
p1 = { x:p0.x + dx, y:p0.y + dy };
nusegs.push({ p0, p1 });
if (showMedialAxis) {
lines.push(new Point(p0.x, p0.y), new Point(p1.x, p1.y));
}
p0 = p1;
}
}
}
// divide a segment/redius into 1 or more equal subsegments // divide a segment/redius into 1 or more equal subsegments
function div(cr) { function div(cr) {
if (cr <= maxR) { if (cr <= maxR) {
@ -439,6 +419,9 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
} }
} }
// medial axis segments for comparison
let segs = [...lineMap.values()];
// project inset segment normals onto closest medial segment // project inset segment normals onto closest medial segment
// use this to find a radius which we divide into extrusion lanes // use this to find a radius which we divide into extrusion lanes
if (!brute) if (!brute)
@ -455,13 +438,13 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
let ltpo; let ltpo;
let trace = []; let trace = [];
trace.shell = sno; trace.shell = sno;
poly.segment(minR).forEachSegment((p1, p2) => { poly.segment(minR, true).forEachSegment((p1, p2) => {
let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 }; let np1 = { x:(p1.x+p2.x)/2, y:(p1.y+p2.y)/2 };
let len = pointDist(p1, p2); let len = pointDist(p1, p2);
let dx = (p1.x - p2.x) / len; let dx = (p1.x - p2.x) / len;
let dy = (p1.y - p2.y) / len; let dy = (p1.y - p2.y) / len;
let min = { dist: Infinity }; let min = { dist: Infinity };
for (let seg of nusegs) { for (let seg of segs) {
let { p0, p1 } = seg; let { p0, p1 } = seg;
let int = intersectRayLine(np1, { dx: dy, dy: -dx }, p0, p1); let int = intersectRayLine(np1, { dx: dy, dy: -dx }, p0, p1);
if (int?.dist < min.dist && int?.dist < noodleWidth) { if (int?.dist < min.dist && int?.dist < noodleWidth) {
@ -486,24 +469,23 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
let len = Math.ceil(pop.length / 2); let len = Math.ceil(pop.length / 2);
// check if segment was claimed by a different originating segment // check if segment was claimed by a different originating segment
// for odd wall counts so that they're not emitted twice // for odd wall counts so that they're not emitted twice
if (odd && mp1.claimed) len--; if (odd && mp1.claimed && mp1.claimed !== p1.segment) len--;
if (len === 0) { if (len === 0) {
nupoly.push(new Point(min.x, min.y)); nupoly.push(new Point(min.x, min.y));
return; return;
} }
let op;
if (pop.length === 1) { if (pop.length === 1) {
// let rad_step = ((pop[0] * sstep) | 0) / sstep; // let rad_step = ((pop[0] * sstep) | 0) / sstep;
// for single wall place it exactly on medial axis // for single wall place it exactly on medial axis
nupoly.push(new Point(min.x, min.y)); nupoly.push(new Point(min.x, min.y));
trace.push(op = { x: min.x, y: min.y, r: mr }) trace.push({ x: min.x, y: min.y, r: mr })
} else { } else {
// radius rounded to nearest step // radius rounded to nearest step
let rad_step = ((pop[0] * sstep) | 0) / sstep; let rad_step = ((pop[0] * sstep) | 0) / sstep;
// otherwise use first division // otherwise use first division
// compensate for minimal inset // compensate for minimal inset
let off = -dstep + rad_step; let off = -dstep + rad_step;
trace.push(op = { trace.push({
x: np1.x + dy * off, x: np1.x + dy * off,
y: np1.y - dx * off, y: np1.y - dx * off,
r: rad_step r: rad_step
@ -512,7 +494,7 @@ function trace_noodle(noodle, noodleWidth, minR, midR, maxR, opt = {}) {
nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2)); nupoly.push(new Point(np1.x + dy * off * 2, np1.y - dx * off * 2));
} }
if (odd && !mp1.claimed) { if (odd && !mp1.claimed) {
mp1.claimed = true; mp1.claimed = p1.segment;
} }
// detect trace jump and start new trace // detect trace jump and start new trace
if (ltpo && pointDist(ltpo, trace.peek()) > maxR) { if (ltpo && pointDist(ltpo, trace.peek()) > maxR) {