Functional Weave
Code in Python

construction.timber-length@1.0.0

impl/python.py

2,022 bytes · the Python implementation · view raw

from typing import List, Sequence

from .construction_timber_length_types import CutBar, CuttingList


def timber_cutting_list(cuts: Sequence[int], stock_length: int, kerf: int) -> CuttingList:
    """Plan which pieces to cut from which stock length, first-fit decreasing.

    A bar holds pieces p1..pn when their lengths plus a kerf between each pair
    fit: sum + kerf * (n - 1) <= stock_length. The last piece may end exactly
    at the end of the bar, which needs no final cut. Ignoring kerf is the usual
    mistake: three 1200 mm pieces do not come out of one 3600 mm length.
    """
    if isinstance(stock_length, bool) or not isinstance(stock_length, int) or stock_length <= 0:
        raise ValueError("stockLength must be a whole number of millimetres greater than 0, received %r" % (stock_length,))
    if isinstance(kerf, bool) or not isinstance(kerf, int) or kerf < 0:
        raise ValueError("kerf must be a whole number of millimetres, 0 or more, received %r" % (kerf,))
    for i, cut in enumerate(cuts):
        if isinstance(cut, bool) or not isinstance(cut, int) or cut <= 0:
            raise ValueError("cut %d must be a whole number of millimetres greater than 0, received %r" % (i + 1, cut))
        if cut > stock_length:
            raise ValueError("cut %d (%d mm) is longer than the stock length (%d mm)" % (i + 1, cut, stock_length))

    # Equal lengths are interchangeable, so a plain descending sort is deterministic.
    pieces: List[List[int]] = []
    used: List[int] = []
    for cut in sorted(cuts, reverse=True):
        for b in range(len(pieces)):
            if used[b] + kerf + cut <= stock_length:
                pieces[b].append(cut)
                used[b] += kerf + cut
                break
        else:
            pieces.append([cut])
            used.append(cut)

    bars = [CutBar(cuts=p, offcut=max(0, stock_length - used[b] - kerf)) for b, p in enumerate(pieces)]
    return CuttingList(bars=bars, bar_count=len(bars), waste=len(bars) * stock_length - sum(cuts))