mirror of
https://github.com/gittrahan/support-fins.git
synced 2026-10-19 02:10:52 -05:00
engine: support bores -- a wall in a bore is built (inBore), refusals get their real reason
enclosedFloor refused any part-attached column walled round by the part; it now only refuses a buried column. A bore's wall stands on its floor along the bore axis and is flagged inBore. The one `bore` skip count covered every floored refusal -- mostly short runs (artichoke X30: 54 'bores', 0 real) -- so each is now counted as stub / blocked / buried / degenerate, the plate path's names.
This commit is contained in:
1 parent
241673bfd4
commit
2904fd7fde
4 files changed
+98
-34
No files matched your search
@@ -25,6 +25,12 @@ fence around it.
|
||||
because its block put the run tangent *into* the part by construction;
|
||||
- an overhang a tooth cannot reach into gets **no tine** (no gripping air).
|
||||
|
||||
**`bores.test.js`** -- bores get supported (reversed 2026-09-27 after print tests):
|
||||
- a bore's ceiling gets a **part-attached wall on the bore floor**, flagged `inBore`;
|
||||
- that wall runs **along the bore axis**, so it pulls out an open end;
|
||||
- no `bore` skip reason: a refusal is counted as `stub` / `blocked` / `buried`
|
||||
(the old `bore` count was mostly short runs, e.g. artichoke X30's 54).
|
||||
|
||||
**`raster.test.js`** -- raster placement (`web/prop/raster.js`), raced per region:
|
||||
- a curved underside the tube route left bare (flat torus at X30: 4%) gets **held**;
|
||||
- a swap **never raises the lowest tine** (the part's base grip) past 0.1 mm and
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Bores get supported (Matthew's print tests, 2026-09-27, reversing the old
|
||||
// never-fin-a-bore rule). A part-attached wall stands on the bore's floor under
|
||||
// its ceiling, runs ALONG the bore so it pulls out an open end, and is flagged
|
||||
// inBore for Suggest orientation. A refusal is counted under its real reason --
|
||||
// there is no `bore` skip any more.
|
||||
|
||||
import { holedPlateTopo, analyze, fins, rotY, assert } from './_util.js';
|
||||
|
||||
// A 40 x 40 block, 30 thick, with a 10 x 10 bore through it; turned on its side
|
||||
// (rotY 90) the bore runs along X, with a flat 30 x 10 ceiling 10 mm above its floor.
|
||||
const topo = holedPlateTopo(20, 20, 30, 5, 5);
|
||||
const rot = rotY(90);
|
||||
const res = analyze(topo, 45, rot);
|
||||
const built = fins.buildFins(topo, res, rot, { mode: 'auto', bedPad: true });
|
||||
|
||||
Deno.test('bores: the bore ceiling gets a wall on the bore floor, flagged inBore', () => {
|
||||
const inBore = built.props.filter((q) => q.inBore);
|
||||
assert(inBore.length >= 1, `no wall in the bore: ${JSON.stringify(built.skipped)}`);
|
||||
for (const w of inBore) assert(w.partAttached, 'a bore wall must stand on the bore floor');
|
||||
});
|
||||
|
||||
Deno.test('bores: the wall runs along the bore axis, so it slides out an open end', () => {
|
||||
for (const w of built.props.filter((q) => q.inBore)) {
|
||||
const a = w.line[0], b = w.line[w.line.length - 1];
|
||||
const along = Math.abs(b[0] - a[0]), across = Math.abs(b[1] - a[1]);
|
||||
assert(along > 3 * across, `wall runs across the bore: dx ${along.toFixed(1)} dy ${across.toFixed(1)}`);
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test('bores: no `bore` skip reason is reported', () => {
|
||||
assert(!('bore' in built.skipped), `skipped still carries bore: ${JSON.stringify(built.skipped)}`);
|
||||
});
|
||||
+7
-6
@@ -76,7 +76,7 @@ export function noProps() {
|
||||
return {
|
||||
triangles: [], props: [], served: 0, volume: 0,
|
||||
skipped: { noLine: 0, wanders: 0, stub: 0, blocked: 0,
|
||||
degenerate: 0, buried: 0, weld: 0, sliver: 0, bore: 0 },
|
||||
degenerate: 0, buried: 0, weld: 0, sliver: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ function buildPass(topo, result, rot, opts, raster) {
|
||||
// within a generation.
|
||||
let nextId = 0;
|
||||
const skipped = { noLine: 0, wanders: 0, stub: 0, blocked: 0,
|
||||
degenerate: 0, buried: 0, weld: 0, sliver: 0, bore: 0 };
|
||||
degenerate: 0, buried: 0, weld: 0, sliver: 0 };
|
||||
const v = [0, 0, 0];
|
||||
|
||||
// The whole part, seated once, for the part-attached floor probe: the floor a
|
||||
@@ -313,9 +313,10 @@ function buildPass(topo, result, rot, opts, raster) {
|
||||
// must stand on THAT floor, not stilt to the plate through the part (the
|
||||
// bug Matthew hit on a real hub). buildPartAttached declines on an ordinary
|
||||
// bed overhang (floorLine ~0), so the plate path below is reached unchanged
|
||||
// for the flagship parts. When there IS a floor but no safe wall fits (a
|
||||
// bore, or side walls in the way) it says `floored` -- counted and skipped,
|
||||
// never stilted through the part or scarred into a bore. Works on a COPY so
|
||||
// for the flagship parts. When there IS a floor but no safe wall fits (side
|
||||
// walls in the way, a buried column, too short) it says `floored` with the
|
||||
// reason -- counted and skipped, never stilted through the part. A bore is
|
||||
// no longer a refusal: its wall is built and flagged inBore. Works on a COPY so
|
||||
// the plate path's own `line` is untouched.
|
||||
const tri0 = out.length;
|
||||
const pa = buildPartAttached(line, partTris, topo, rot, off, out);
|
||||
@@ -350,7 +351,7 @@ function buildPass(topo, result, rot, opts, raster) {
|
||||
triRanges: [[tri0, out.length]] });
|
||||
continue;
|
||||
}
|
||||
if (pa.floored) { skipped.bore++; continue; }
|
||||
if (pa.floored) { skipped[pa.floored]++; continue; }
|
||||
|
||||
// Finish the top against the WHOLE region, not just this patch: a track
|
||||
// near a patch boundary can run under a sibling patch's faces, and
|
||||
|
||||
+53
-28
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Part-attached walls: an overhang above another part of the part stands its
|
||||
* wall on that floor (`floorLine`) instead of stilting to the plate through it,
|
||||
* and refuses a column inside a bore or slot (`enclosedFloor`) or one a side
|
||||
* wall cuts through (`clearBetween`). `buildPartAttached` is the verdict the
|
||||
* auto-placer acts on.
|
||||
* and refuses a buried column (`buriedColumn`) or one a side wall cuts through
|
||||
* (`clearBetween`). A wall inside a bore is built and flagged `inBore`.
|
||||
* `buildPartAttached` is the verdict the auto-placer acts on.
|
||||
*
|
||||
* Split out of prop.js, which re-exports the public names.
|
||||
*/
|
||||
@@ -68,21 +68,27 @@ const BORE = {
|
||||
step: 0.5, // mm along each ray
|
||||
};
|
||||
|
||||
const zMidAt = (top, floor, k) => (floor[k][2] + (top[k][2] - PROP.gap)) / 2;
|
||||
|
||||
/** A column whose own centreline is inside the part is buried, not standable. */
|
||||
function buriedColumn(top, floor, k, topo, rot, offset) {
|
||||
return insidePart(topo, rot, offset, top[k][0], top[k][1], zMidAt(top, floor, k));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the support column at station `k` enclosed by part walls -- i.e. standing
|
||||
* inside a bore or narrow slot? A support there SCARS an internal surface you
|
||||
* cannot clean (worse than a little sag), so [[project_support_fin_quality_first]]
|
||||
* says refuse it: a hole is an ORIENTATION problem, not a support one.
|
||||
* Is the support column at station `k` enclosed by part walls -- standing inside
|
||||
* a bore or narrow slot? Cast `dirs` horizontal rays out from the column's
|
||||
* centreline at mid-height and count how many strike part within `radius`: an
|
||||
* open ledge-over-base has air on some sides, a bore is walled nearly all round.
|
||||
*
|
||||
* Cast `dirs` horizontal rays out from the column's centreline at mid-height and
|
||||
* count how many strike part material within `radius`. An open ledge-over-base
|
||||
* has air on at least some sides (few walled); a blind bore is walled all round.
|
||||
* This USED to refuse the wall. Matthew's print tests (2026-09-27) reversed that:
|
||||
* a bore gets supported, and the wall runs along the bore (the contact line under
|
||||
* a bore's ceiling follows its axis) so it pulls out an open end. Now it only
|
||||
* labels the wall `inBore`, which Suggest orientation weighs as a mild preference
|
||||
* for poses that point the holes up.
|
||||
*/
|
||||
function enclosedFloor(top, floor, k, topo, rot, offset) {
|
||||
const p = top[k];
|
||||
const zMid = (floor[k][2] + (top[k][2] - PROP.gap)) / 2;
|
||||
// A column whose own centreline is inside the part is buried, not standable.
|
||||
if (insidePart(topo, rot, offset, p[0], p[1], zMid)) return true;
|
||||
function enclosedColumn(top, floor, k, topo, rot, offset) {
|
||||
const p = top[k], zMid = zMidAt(top, floor, k);
|
||||
let walled = 0;
|
||||
for (let d = 0; d < BORE.dirs; d++) {
|
||||
const ang = (d / BORE.dirs) * 2 * Math.PI;
|
||||
@@ -129,10 +135,12 @@ function clearBetween(top, floor, k, topo, rot, offset) {
|
||||
* Try to stand a PART-ATTACHED wall under `line` (the overhang contact polyline,
|
||||
* seated). Returns one of three verdicts:
|
||||
* { ok: true, prop } -- a part-attached wall was built into `out`.
|
||||
* { floored: true } -- there IS a floor here (an over-the-part overhang) but
|
||||
* no safe wall fits (a bore, or side walls in the way);
|
||||
* the caller must NOT then stilt to the plate through
|
||||
* the part -- it counts this and moves on.
|
||||
* { floored: why } -- there IS a floor here (an over-the-part overhang) but
|
||||
* no safe wall fits; the caller must NOT then stilt to
|
||||
* the plate through the part -- it counts this under
|
||||
* `why` (the plate path's own skip names: 'buried',
|
||||
* 'blocked' = side walls in the way, 'stub' = too short
|
||||
* or too low, 'degenerate') and moves on.
|
||||
* { } -- no floor beneath the overhang; an ordinary bed
|
||||
* overhang. The caller falls through to the plate path
|
||||
* UNCHANGED, so the flagship parts don't change.
|
||||
@@ -140,8 +148,9 @@ function clearBetween(top, floor, k, topo, rot, offset) {
|
||||
* This is the auto-placer's version of what draw.js does by hand: settle a BANDED
|
||||
* top so the overhang isn't dragged onto its own floor, read the floor with
|
||||
* floorLine, and bridge the two with sweepBetween. It only claims a line when a
|
||||
* real floor sits under MOST of it, refuses bores (enclosedFloor), and refuses to
|
||||
* pierce side walls (clearBetween).
|
||||
* real floor sits under MOST of it, refuses a buried column (buriedColumn), and
|
||||
* refuses to pierce side walls (clearBetween). A wall inside a bore is built and
|
||||
* flagged `inBore` (enclosedColumn).
|
||||
*/
|
||||
export function buildPartAttached(line, partTris, topo, rot, offset, out) {
|
||||
const top = line.map((p) => [p[0], p[1], p[2]]);
|
||||
@@ -157,23 +166,39 @@ export function buildPartAttached(line, partTris, topo, rot, offset, out) {
|
||||
for (const f of floor) if (f[2] > PROP.gap + 0.5) real++;
|
||||
if (real < Math.max(PROP.minStations, Math.ceil(floor.length * 0.5))) return {};
|
||||
|
||||
// Why each station failed, so a refusal is counted under its real reason (the
|
||||
// one count used to be `bore` for all of them; most were short runs).
|
||||
const why = [];
|
||||
const ok = top.map((p, k) => {
|
||||
if (floor[k][2] <= PROP.gap + 0.5) return false; // no real floor
|
||||
if ((p[2] - PROP.gap) - floor[k][2] < PROP.minHeight) return false;
|
||||
if (enclosedFloor(top, floor, k, topo, rot, offset)) return false;
|
||||
return clearBetween(top, floor, k, topo, rot, offset);
|
||||
if ((p[2] - PROP.gap) - floor[k][2] < PROP.minHeight) { why.push('stub'); return false; }
|
||||
if (buriedColumn(top, floor, k, topo, rot, offset)) { why.push('buried'); return false; }
|
||||
if (clearBetween(top, floor, k, topo, rot, offset)) return true;
|
||||
why.push('blocked');
|
||||
return false;
|
||||
});
|
||||
const run = longestRun(ok);
|
||||
if (!run || run[1] - run[0] < PROP.minStations) return { floored: true };
|
||||
if (!run || run[1] - run[0] < PROP.minStations) {
|
||||
// Mostly-failing stations name the refusal; a clear run that is merely short is a stub.
|
||||
const n = {};
|
||||
for (const w of why) n[w] = (n[w] ?? 0) + 1;
|
||||
const worst = Object.keys(n).sort((a, b) => n[b] - n[a])[0];
|
||||
return { floored: why.length * 2 >= floor.length && worst ? worst : 'stub' };
|
||||
}
|
||||
|
||||
const subTop = top.slice(run[0], run[1]);
|
||||
const subFloor = floor.slice(run[0], run[1]);
|
||||
const span = Math.hypot(subTop[subTop.length - 1][0] - subTop[0][0],
|
||||
subTop[subTop.length - 1][1] - subTop[0][1]);
|
||||
if (span < PROP.minSpan) return { floored: true };
|
||||
if (span < PROP.minSpan) return { floored: 'stub' };
|
||||
|
||||
const before = out.length;
|
||||
if (!sweepBetween(subTop, subFloor, out)) { out.length = before; return { floored: true }; }
|
||||
if (!sweepBetween(subTop, subFloor, out)) { out.length = before; return { floored: 'degenerate' }; }
|
||||
|
||||
// In a bore when most of the kept run is walled all round.
|
||||
let walled = 0;
|
||||
for (let k = run[0]; k < run[1]; k++) if (enclosedColumn(top, floor, k, topo, rot, offset)) walled++;
|
||||
const inBore = walled * 2 > run[1] - run[0];
|
||||
|
||||
let height = 0, vol = 0;
|
||||
for (let i = 0; i < subTop.length; i++) {
|
||||
@@ -188,7 +213,7 @@ export function buildPartAttached(line, partTris, topo, rot, offset, out) {
|
||||
ok: true,
|
||||
prop: {
|
||||
span, height, stations: subTop.length, volume: Math.abs(vol),
|
||||
partAttached: true,
|
||||
partAttached: true, inBore,
|
||||
line: subTop.map((p) => [p[0], p[1], p[2] - PROP.gap]),
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user