Files
support-fins/plugins/shared/engine/seat.js
T

100 lines
4.6 KiB
JavaScript

// What every plugin entry does to a part before the engine sees it, in one place,
// so computeFins (fins_entry.js) and drawWall (draw_entry.js) can't seat a part or
// pick its clearances differently.
import { MATERIAL } from '../../../web/materials.js';
import { CUTOUT_PATTERNS } from '../../../web/cutout.js';
// The site's Bed pad choices minus Custom (a plugin dialog shows presets, not the
// four custom numbers). 'off' is the site's Off, the same as bedPad: false.
export const PAD_STYLES = ['off', 'auto', 'light', 'sure'];
export function pick(name, value, allowed) {
if (!allowed.includes(value)) throw new Error(`${name} must be one of ${allowed.join(', ')}, got ${JSON.stringify(value)}`);
return value;
}
// The site's Support gap field's range (#gap in web/index.html, options.json's gap).
// Refused rather than clamped, as optionsFromDialog does: a host calling computeFins
// directly with 3 (forgot it is mm, not tenths) would print welded supports.
const GAP_MIN = 0.1, GAP_MAX = 0.4;
function gapOf(gap) {
if (!(typeof gap === 'number' && gap >= GAP_MIN && gap <= GAP_MAX)) {
throw new Error(`gap must be ${GAP_MIN}..${GAP_MAX}, got ${JSON.stringify(gap)}`);
}
return gap;
}
/**
* The clearances buildFins / applyTunables take, exactly as the site's build request
* sends them (ui/finbuild.js): PLA's pad numbers, as on the site, and the Support
* gap. ALWAYS the full set: they land in module state, and a plugin's V8 context
* lives across calls, so a partial set would let one run's gap leak into the next.
* `opts` is ENGINE_DEFAULTS merged with the host's options.
*/
export function clearances(opts) {
// The PLA/PETG choice is gone (the site dropped it first): refuse it out loud
// rather than quietly build a host's PETG request at PLA's gap.
if (opts.material !== undefined) throw new Error('material is no longer an option: pass gap (0.3 for PETG)');
const tunables = {
padH: MATERIAL.pla.padH, padGrab: MATERIAL.pla.padGrab, propGap: gapOf(opts.gap),
// Off builds no pad, so which style it carries doesn't matter; Auto keeps it valid.
padStyle: pick('padStyle', opts.padStyle, PAD_STYLES) === 'off' ? 'auto' : opts.padStyle,
cutout: pick('cutout', opts.cutout, CUTOUT_PATTERNS),
};
return { tunables };
}
/**
* Seat the part at the origin OURSELVES, in float64, before the engine sees it.
* The engine welds vertices on a 1-micron grid of ABSOLUTE coordinates, so the
* same part parked at x=137 on Orca's plate welds differently than at x=0 and
* can grow or lose a tine (measured on lbracket). Centring first makes the result
* independent of where the part sits on the plate -- and identical to the website
* for a part whose STL is centred.
*
* Returns { pos, shift }: pos = input - shift, on a 1 nm grid.
*/
export function seatSoup(positions) {
const input = (positions instanceof Float32Array || positions instanceof Float64Array)
? positions : Float64Array.from(positions);
if (input.length === 0 || input.length % 9 !== 0) {
throw new Error(`positions must be a non-empty triangle soup (9 floats/face), got ${input.length}`);
}
let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity, z0 = Infinity;
for (let i = 0; i < input.length; i += 3) {
const x = input[i], y = input[i + 1], z = input[i + 2];
if (x < x0) x0 = x; if (x > x1) x1 = x;
if (y < y0) y0 = y; if (y > y1) y1 = y;
if (z < z0) z0 = z;
}
const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
// ...then snap to a 1 nm grid. The engine's tine placement moves under ~1e-13 mm
// of noise (ENGINE-SENSITIVITY.md), and the subtraction above leaves exactly that
// much residue, so without the snap dragging a part across the plate could add or
// drop tines. 1 nm is far below anything printable.
const pos = new Float64Array(input.length);
for (let i = 0; i < input.length; i += 3) {
pos[i] = snap(input[i] - cx);
pos[i + 1] = snap(input[i + 1] - cy);
pos[i + 2] = snap(input[i + 2] - z0);
}
return { pos, shift: { x: cx, y: cy, z: z0 } };
}
const SNAP = 1e6;
export const snap = (v) => Math.round(v * SNAP) / SNAP;
// buildFins hands back either a flat number array or an array of [x,y,z] triples
// depending on the path taken; normalise both to a flat Float32Array.
export function flatten(tris) {
if (!tris || tris.length === 0) return new Float32Array(0);
if (typeof tris[0] === 'number') return Float32Array.from(tris);
const out = new Float32Array(tris.length * 3);
let i = 0;
for (const p of tris) {
if (Array.isArray(p) || ArrayBuffer.isView(p)) { out[i++] = p[0]; out[i++] = p[1]; out[i++] = p[2]; }
else { out[i++] = p.x; out[i++] = p.y; out[i++] = p.z; }
}
return out.subarray(0, i);
}