Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
/// 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.
///
/// # Panics
/// Panics on a stock length or cut that is not positive, a negative kerf, or
/// a cut longer than the stock length.
pub fn timber_cutting_list(cuts: &[i64], stock_length: i64, kerf: i64) -> CuttingList {
if stock_length <= 0 {
panic!("stockLength must be a whole number of millimetres greater than 0, received {}", stock_length);
}
if kerf < 0 {
panic!("kerf must be a whole number of millimetres, 0 or more, received {}", kerf);
}
for (i, &cut) in cuts.iter().enumerate() {
if cut <= 0 {
panic!("cut {} must be a whole number of millimetres greater than 0, received {}", i + 1, cut);
}
if cut > stock_length {
panic!("cut {} ({} mm) is longer than the stock length ({} mm)", i + 1, cut, stock_length);
}
}
// Equal lengths are interchangeable, so a plain descending sort is deterministic.
let mut sorted: Vec<i64> = cuts.to_vec();
sorted.sort_by(|a, b| b.cmp(a));
let mut pieces: Vec<Vec<i64>> = Vec::new();
let mut used: Vec<i64> = Vec::new();
for cut in sorted {
let mut placed = false;
for b in 0..pieces.len() {
if used[b] + kerf + cut <= stock_length {
pieces[b].push(cut);
used[b] += kerf + cut;
placed = true;
break;
}
}
if !placed {
pieces.push(vec![cut]);
used.push(cut);
}
}
let total: i64 = cuts.iter().sum();
let bars: Vec<CutBar> = pieces
.into_iter()
.enumerate()
.map(|(b, p)| CutBar { cuts: p, offcut: (stock_length - used[b] - kerf).max(0) })
.collect();
let bar_count = bars.len() as i64;
CuttingList { bars, bar_count, waste: bar_count * stock_length - total }
}
pub fn cutting_list_to_value(list: &CuttingList) -> Value {
Value::obj(vec![
(
"bars",
Value::Arr(
list.bars
.iter()
.map(|bar| {
Value::obj(vec![
("cuts", Value::Arr(bar.cuts.iter().map(|&c| Value::Int(c)).collect())),
("offcut", Value::Int(bar.offcut)),
])
})
.collect(),
),
),
("barCount", Value::Int(list.bar_count)),
("waste", Value::Int(list.waste)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let mut cuts: Vec<i64> = Vec::new();
for (i, v) in args[0].as_arr().iter().enumerate() {
if let Value::Float(f) = v {
panic!("cut {} must be a whole number of millimetres greater than 0, received {}", i + 1, f);
}
cuts.push(v.as_i64());
}
if let Value::Float(f) = &args[1] {
panic!("stockLength must be a whole number of millimetres greater than 0, received {}", f);
}
if let Value::Float(f) = &args[2] {
panic!("kerf must be a whole number of millimetres, 0 or more, received {}", f);
}
cutting_list_to_value(&timber_cutting_list(&cuts, args[1].as_i64(), args[2].as_i64()))
}