use super::funejson::Value; /// 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 = cuts.to_vec(); sorted.sort_by(|a, b| b.cmp(a)); let mut pieces: Vec> = Vec::new(); let mut used: Vec = 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 = 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 = 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())) }