floor-planner/src/lib/cut-list.ts
2026-09-11 16:58:56 +01:00

71 lines
2.6 KiB
TypeScript

import type {
FlooringPiece,
FlooringPlan,
FlooringSettings,
SkirtingPiece,
SkirtingPlan,
} from './types';
const sameLength = (a: number, b: number) => Math.abs(a - b) < 0.01;
/** A ready offcut still needs a placement code; only fresh, uncut whole boards are omitted. */
export const flooringCuts = (plan: FlooringPlan) =>
plan.pieces.filter((piece) => piece.cut || piece.reused);
export function isSkirtingCut(piece: SkirtingPiece, plan: SkirtingPlan): boolean {
const stock = plan.stocks.find((candidate) => candidate.id === piece.stockId);
return (
!stock ||
!sameLength(piece.length, stock.length) ||
Math.abs(piece.leftMitre) > 0.001 ||
Math.abs(piece.rightMitre) > 0.001
);
}
export const skirtingCuts = (plan: SkirtingPlan) =>
plan.pieces.filter((piece) => isSkirtingCut(piece, plan));
export function uncutFlooring(plan: FlooringPlan, settings: FlooringSettings) {
const uncut = plan.pieces.filter((piece) => !piece.cut);
const wholeBoards = uncut.filter(
(piece) => sameLength(piece.length, settings.length) && sameLength(piece.width, settings.width),
).length;
return { wholeBoards, readyOffcuts: uncut.length - wholeBoards };
}
export function uncutSkirting(plan: SkirtingPlan): { length: number; count: number }[] {
const counts = new Map<number, number>();
for (const piece of plan.pieces) {
if (!isSkirtingCut(piece, plan)) counts.set(piece.length, (counts.get(piece.length) ?? 0) + 1);
}
return [...counts].sort(([a], [b]) => a - b).map(([length, count]) => ({ length, count }));
}
/** Ready remnants need placement instructions, never another trimming instruction. */
export function readyOffcutInstructions(piece: FlooringPiece): string {
const ends =
piece.factoryLeft && piece.factoryRight
? 'both'
: piece.factoryLeft
? 'left'
: piece.factoryRight
? 'right'
: 'neither';
return `Use offcut; no new cut. Required factory short ends: ${ends}.`;
}
/** Keep every cut from a purchased board together, in allocation order across rooms. */
function groupByStock<T extends { stockId: string }>(pieces: T[], stocks: { id: string }[]): T[][] {
const groups = new Map<string, T[]>(stocks.map((stock) => [stock.id, []]));
for (const piece of pieces) {
const group = groups.get(piece.stockId);
if (group) group.push(piece);
else groups.set(piece.stockId, [piece]);
}
return [...groups.values()].filter((group) => group.length > 0);
}
export const groupFlooringCuts = (plan: FlooringPlan) =>
groupByStock(flooringCuts(plan), plan.stocks);
export const groupSkirtingCuts = (plan: SkirtingPlan) =>
groupByStock(skirtingCuts(plan), plan.stocks);