71 lines
2.6 KiB
TypeScript
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);
|