better
This commit is contained in:
parent
b9485e4097
commit
0c0f08b350
45 changed files with 1565 additions and 1579 deletions
|
|
@ -13,6 +13,7 @@ const settings: FlooringSettings = {
|
|||
width: 200,
|
||||
gap: 10,
|
||||
pattern: 'brick',
|
||||
optimisation: 'cuts',
|
||||
minOffset: 300,
|
||||
maxOffset: 600,
|
||||
minLength: 200,
|
||||
|
|
@ -68,6 +69,18 @@ function expectStockFits(plan: FlooringPlan, s: FlooringSettings) {
|
|||
expect(used + Math.max(0, pieces.length - 1) * s.kerf).toBeLessThanOrEqual(s.length + 0.02);
|
||||
expect(stock.remaining).toBeGreaterThanOrEqual(0);
|
||||
expect(used + stock.remaining).toBeLessThanOrEqual(s.length + 0.02);
|
||||
// Crosscuts cannot create factory joints: each purchased board supplies one of each end.
|
||||
for (const end of ['factoryLeft', 'factoryRight'] as const) {
|
||||
expect(
|
||||
pieces.filter((piece) => piece[end]).length,
|
||||
`${stock.id} allocates its ${end} more than once`,
|
||||
).toBeLessThanOrEqual(1);
|
||||
}
|
||||
for (const piece of pieces.filter((piece) => piece.factoryLeft && piece.factoryRight)) {
|
||||
expect(piece.length).toBeCloseTo(s.length, 3);
|
||||
expect(piece.reused).toBe(false);
|
||||
expect(pieces).toHaveLength(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
function expectOffsets(plan: FlooringPlan, s: FlooringSettings) {
|
||||
|
|
@ -137,19 +150,82 @@ describe('laminate planning', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('does not reuse a cut end where an interior board needs two factory ends', () => {
|
||||
const plan = planFlooring([room()], settings, 1);
|
||||
for (const row of plan.rows) {
|
||||
const pieces = row.pieceIds
|
||||
.map((id) => plan.pieces.find((piece) => piece.id === id)!)
|
||||
.sort((a, b) => a.column - b.column);
|
||||
for (const interior of pieces.slice(1, -1)) {
|
||||
expect(interior.length).toBe(settings.length);
|
||||
expect(interior.reused).toBe(false);
|
||||
expect(interior.factoryLeft).toBe(true);
|
||||
expect(interior.factoryRight).toBe(true);
|
||||
}
|
||||
}
|
||||
describe.each([
|
||||
['brick', 'cuts'],
|
||||
['random', 'cuts'],
|
||||
['random', 'random'],
|
||||
] as const)('%s pattern, %s optimisation: factory short ends', (pattern, optimisation) => {
|
||||
const s = { ...settings, pattern, optimisation };
|
||||
|
||||
it.each([0, 90] as const)(
|
||||
'never packs three row ends onto one board, with boards running at %i degrees',
|
||||
(direction) => {
|
||||
const r = direction === 0 ? room(3920, 1620) : room(1620, 3920);
|
||||
r.direction = direction;
|
||||
const plan = planFlooring([r], s, 1);
|
||||
expect(errors(plan)).toEqual([]);
|
||||
expect(plan.rows).toHaveLength(8);
|
||||
expect(plan.reusedCount).toBeGreaterThan(0);
|
||||
for (const row of plan.rows) {
|
||||
const position = (polygon: Point[]) =>
|
||||
Math.min(...polygon.map((point) => (direction === 0 ? point.x : point.y)));
|
||||
const pieces = row.pieceIds
|
||||
.map((id) => plan.pieces.find((piece) => piece.id === id)!)
|
||||
.sort((a, b) => position(a.polygon) - position(b.polygon));
|
||||
expect(pieces.length).toBeGreaterThan(2);
|
||||
// Derive the required joints from installed positions, independently of allocation flags.
|
||||
pieces.forEach((piece, index) => {
|
||||
expect(piece.factoryLeft, `${piece.id}: left joint`).toBe(index > 0);
|
||||
expect(piece.factoryRight, `${piece.id}: right joint`).toBe(index < pieces.length - 1);
|
||||
});
|
||||
}
|
||||
const shortestEnds = plan.pieces
|
||||
.filter((piece) => piece.factoryLeft !== piece.factoryRight)
|
||||
.map((piece) => piece.length)
|
||||
.sort((a, b) => a - b)
|
||||
.slice(0, 3);
|
||||
expect(shortestEnds).toHaveLength(3);
|
||||
// Three ends fit by length, including kerf, but cannot share only two factory joints.
|
||||
expect(shortestEnds.reduce((sum, length) => sum + length, 0) + 2 * s.kerf).toBeLessThan(
|
||||
s.length,
|
||||
);
|
||||
for (const stock of plan.stocks) expect(stock.pieceIds.length).toBeLessThanOrEqual(2);
|
||||
expectStockFits(plan, s);
|
||||
expectCoverage(plan, r, s.gap);
|
||||
},
|
||||
);
|
||||
|
||||
it('allows three pieces when the middle cut spans wall to wall and the outer cuts keep opposite ends', () => {
|
||||
const first = room(1620, 220, 'first');
|
||||
const middle = room(220, 414, 'middle');
|
||||
middle.direction = 90;
|
||||
const last = room(1620, 220, 'last');
|
||||
const rooms = [first, middle, last];
|
||||
const plan = planFlooring(rooms, s, 1);
|
||||
expect(errors(plan)).toEqual([]);
|
||||
const triples = plan.stocks.filter((stock) => stock.pieceIds.length === 3);
|
||||
expect(triples).toHaveLength(1);
|
||||
const pieces = triples[0].pieceIds.map((id) => plan.pieces.find((piece) => piece.id === id)!);
|
||||
expect(
|
||||
pieces.map((piece) => ({
|
||||
room: piece.roomId,
|
||||
length: piece.length,
|
||||
left: piece.factoryLeft,
|
||||
right: piece.factoryRight,
|
||||
})),
|
||||
).toEqual([
|
||||
{ room: 'first', length: 400, left: true, right: false },
|
||||
{ room: 'middle', length: 394, left: false, right: false },
|
||||
{ room: 'last', length: 400, left: false, right: true },
|
||||
]);
|
||||
expect(plan.pieces.filter((piece) => piece.roomId === middle.id)).toEqual([pieces[1]]);
|
||||
expect(pieces.map((piece) => piece.reused)).toEqual([false, true, true]);
|
||||
expect(plan.boardCount).toBe(3);
|
||||
expect(plan.cuts).toBe(2);
|
||||
expect(triples[0].remaining).toBe(0);
|
||||
expectStockFits(plan, s);
|
||||
for (const r of rooms) expectCoverage(plan, r, s.gap);
|
||||
});
|
||||
});
|
||||
|
||||
it('produces exact concave fireplace templates and keeps the gap at its external corners', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue