GithubHelp home page GithubHelp logo

cut-optimizer-2d's Introduction

Worflows Crates.io Documentation Dependency status

Cut Optimizer 2D

Description

Cut Optimizer 2D is a cut optimizer library for optimizing rectangular cut pieces from sheet goods.

Given desired cut pieces and stock sheets, it will attempt to layout the cut pieces in way that gives the least waste. It can't guarantee the most optimizal solution possible, since this would be too inefficient. Instead it uses genetic algorithms and multiple heuristics to solve the problem. This usually results in a satisfactory solution.

License

Duel-license under MIT license (LICENSE-MIT), or Apache License, Version 2.0 (LICENSE-APACHE)

cut-optimizer-2d's People

Contributors

jasonrhansen avatar kenianbei avatar nkerruo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

cut-optimizer-2d's Issues

Option to limit stock pieces by quantity

I'm using this in a personal project, and I'm wondering if it would be difficult to limit stock pieces by a quantity?

I'm not a rust expert and I can't quite get my head around where in the code this should be handled. Any help or tips to get me started would be welcome. Thanks!

No solution for layout for case "pighetti_b"

Currently on cut-optimizer-2d git rev 80d7031

Here is the cut spec

case pighetti_b

kerf width of 2
1x Stock, 2440 x 1220
12x Piece A, 775 x 150
25x Piece B, 450 x 100

Here is the solution laid out by hand

Screen Shot 2022-03-13 at 11 08 24 AM

Here is the code

fn case_pighetti_b() {
    let plywood = StockPiece {
        quantity: Some(1),
        length: 2240,
        width: 1220,
        pattern_direction: PatternDirection::ParallelToLength,
        price: 130,
    };

    let cut_piece_a = CutPiece {
        quantity: 12,
        external_id: Some(1),
        length: 775,
        width: 150,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let cut_piece_b = CutPiece {
        quantity: 25,
        external_id: Some(2),
        length: 450,
        width: 100,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let mut optimizer = Optimizer::new();
    optimizer.add_stock_piece(plywood);
    optimizer.add_cut_piece(cut_piece_a);
    optimizer.add_cut_piece(cut_piece_b);
    optimizer.set_cut_width(2);

    optimizer.optimize_and_print("second_case");
}

trait OptimizeAndPrint {
    fn optimize_and_print(&self, case_name: &str);
}

impl OptimizeAndPrint for cut_optimizer_2d::Optimizer {
    fn optimize_and_print(&self, case_name: &str) {
        println!("[{}]", case_name);

        fn simple_callback(_: f64) {
            // print!("Progress {}", progress);
        }

        let result = self.optimize_guillotine(simple_callback);
        match result {
            Err(error) => println!("error!, {:?}", error),
            Ok(solution) => {
                println!("Fitness: {}", solution.fitness);
                println!("Stock pieces: {}", solution.stock_pieces.len());
                for stock_piece in solution.stock_pieces {
                    println!(
                        "Stock: {}x{}, cut pieces: {}",
                        stock_piece.length,
                        stock_piece.width,
                        stock_piece.cut_pieces.len()
                    );
                    for cut_piece in stock_piece.cut_pieces {
                        println!(
                            "Cut piece: {}x{} at X={} Y={}",
                            cut_piece.length, cut_piece.width, cut_piece.x, cut_piece.y
                        );
                    }
                }
            }
        }

        println!("");
    }
}

Result

error!, NoFitForCutPiece(CutPiece { quantity: 1, external_id: Some(2), width: 100, length: 450, pattern_direction: ParallelToLength, can_rotate: false })

Incorrect ResultCutPiece quantity on second StockPiece for case "pighetti_d"

On rev e382e13 if I try to span 6 CutPieces over 2 StockPieces, I end up with 8 CutPieces

Case

fn case_pighetti_d() {
    let case_name = "pighetti_d";

    let plywood = StockPiece {
        quantity: Some(2),
        length: 2440,
        width: 1220,
        pattern_direction: PatternDirection::ParallelToLength,
        price: 130,
    };

    let cut_piece_a = CutPiece {
        quantity: 6,
        external_id: Some(1),
        length: 814,
        width: 465,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let mut optimizer = Optimizer::new();
    optimizer.add_stock_piece(plywood);
    optimizer.add_cut_piece(cut_piece_a);
    optimizer.set_cut_width(2);

    optimizer.optimize_and_print(case_name);
}

Expected Result

Actual Result

Make Solution.price field public

Assuming the price is equal to the cost of consumed stock (cut piece + cutter kerf), it would be nice to make this field public so we can see the cost of the stock being cut.

This also opens up a conversation about creating something like a CostMetrics struct... perhaps with stock_piece_cost = cut_piece_cost + cut_width_cost + waste_cost

Incorrect ResultCutPiece quantity for case "pighetti_c"

Summary

Trying to cut 1 piece, getting 16 pieces instead

Version

Currently on cut-optimizer-2d git rev 80d7031

Cut spec

case pighetti_c

kerf width of 2
1x Stock, 2440 x 1220
1x Piece, 775 x 150

Human solution

Screen Shot 2022-03-13 at 11 25 27 AM

MCVE

fn case_pighetti_c() {
    let case_name = "pighetti_c";

    let plywood = StockPiece {
        quantity: Some(1),
        length: 2240,
        width: 1220,
        pattern_direction: PatternDirection::ParallelToLength,
        price: 130,
    };

    let cut_piece_a = CutPiece {
        quantity: 1,
        external_id: Some(1),
        length: 775,
        width: 150,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let mut optimizer = Optimizer::new();
    optimizer.add_stock_piece(plywood);
    optimizer.add_cut_piece(cut_piece_a);
    optimizer.set_cut_width(2);

    optimizer.optimize_and_print(case_name);
}

trait OptimizeAndPrint {
    fn optimize_and_print(&self, case_name: &str);
}

impl OptimizeAndPrint for cut_optimizer_2d::Optimizer {
    fn optimize_and_print(&self, case_name: &str) {
        println!("[{}]", case_name);

        fn simple_callback(_: f64) {
            // print!("Progress {}", progress);
        }

        let result = self.optimize_guillotine(simple_callback);
        match result {
            Err(error) => println!("error!, {:?}", error),
            Ok(solution) => {
                println!("Fitness: {}", solution.fitness);
                println!("Stock pieces: {}", solution.stock_pieces.len());
                for stock_piece in solution.stock_pieces {
                    println!(
                        "Stock: {}x{}, cut pieces: {}",
                        stock_piece.length,
                        stock_piece.width,
                        stock_piece.cut_pieces.len()
                    );
                    for cut_piece in stock_piece.cut_pieces {
                        println!(
                            "Cut piece: {}x{} at X={} Y={}",
                            cut_piece.length, cut_piece.width, cut_piece.x, cut_piece.y
                        );
                    }
                }
            }
        }

        println!("");
    }
}

Result

[pighetti_c]
Fitness: 0.47042281670019365
Stock pieces: 1
Stock: 2240x1220, cut pieces: 16
Cut piece: 775x150 at X=0 Y=0
Cut piece: 775x150 at X=0 Y=777
Cut piece: 775x150 at X=152 Y=0
Cut piece: 775x150 at X=152 Y=777
Cut piece: 775x150 at X=304 Y=0
Cut piece: 775x150 at X=304 Y=777
Cut piece: 775x150 at X=456 Y=0
Cut piece: 775x150 at X=456 Y=777
Cut piece: 775x150 at X=608 Y=0
Cut piece: 775x150 at X=760 Y=0
Cut piece: 775x150 at X=912 Y=0
Cut piece: 775x150 at X=1064 Y=0
Cut piece: 775x150 at X=608 Y=777
Cut piece: 775x150 at X=760 Y=777
Cut piece: 775x150 at X=912 Y=777
Cut piece: 775x150 at X=1064 Y=777

Screen Shot 2022-03-13 at 11 25 23 AM

Cut count optimization

I found a trivial case where human layout scores better than cut_optimizer_2d driven layout. I have documented it below.

My scoring criteria was reducing waste (no change) and reducing cut lines (human solution preferred).

Thank you for authoring this lovely piece of software.

Screen Shot 2022-03-07 at 8 24 24 AM

Source

use cut_optimizer_2d::CutPiece;
use cut_optimizer_2d::Optimizer;
use cut_optimizer_2d::PatternDirection;
use cut_optimizer_2d::StockPiece;

fn main() {
    let plywood = StockPiece {
        width: 1220,
        length: 2240,
        pattern_direction: PatternDirection::ParallelToLength,
        price: 130,
        quantity: Some(1),
    };

    let top = CutPiece {
        external_id: Some(1),
        width: 500,
        length: 835,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let left = CutPiece {
        external_id: Some(2),
        width: 500,
        length: 1250,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let right = CutPiece {
        external_id: Some(2),
        width: 500,
        length: 1250,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let bottom = CutPiece {
        external_id: Some(3),
        width: 575,
        length: 875,
        can_rotate: false,
        pattern_direction: PatternDirection::ParallelToLength,
    };

    let mut optimizer = Optimizer::new();
    optimizer.add_stock_piece(plywood);
    optimizer.add_cut_piece(top);
    optimizer.add_cut_piece(left);
    optimizer.add_cut_piece(right);
    optimizer.add_cut_piece(bottom);
    optimizer.set_cut_width(2);

    fn simple_callback(_: f64) {
        // print!("Progress {}", progress);
    }

    let result = optimizer.optimize_guillotine(simple_callback);

    match result {
        Err(error) => println!("error!, {:?}", error),
        Ok(solution) => {
            println!("Fitness: {}", solution.fitness);
            println!("Stock pieces: {}", solution.stock_pieces.len());

            for stock_piece in solution.stock_pieces {
                println!("Stock: {}x{}", stock_piece.length, stock_piece.width);
                for cut_piece in stock_piece.cut_pieces {
                    println!(
                        "Cut piece: {}x{} at X={} Y={}",
                        cut_piece.length, cut_piece.width, cut_piece.x, cut_piece.y
                    );
                }
            }
        }
    }
}

Output

Fitness: 0.6325602099578904
Stock pieces: 1
Stock: 2240x1220
Cut piece: 875x575 at X=0 Y=0
Cut piece: 1250x500 at X=0 Y=877
Cut piece: 1250x500 at X=577 Y=0
Cut piece: 835x500 at X=577 Y=1252

Guillotine Cut Error

I'm reviewing this guillotine cut in javascript using wasm-pack. The guillotine cut result is not correct on the version 4.2, while it was correct on 1.5 !
Could you help where is my wrong?

const wasm = await import('cut-optimizer-2d-web');
const opt = new wasm.Optimizer();
opt.addStockPiece(1000, 1000, 0);
opt.addStockPiece(1500, 3000, 0);
opt.addCutPiece(1, 1000, 1000, false, 0);
opt.addCutPiece(2, 1000, 1000, false, 0);
opt.addCutPiece(3, 500, 2000, false, 0);
opt.addCutPiece(4, 500, 2000, false, 0);
opt.addCutPiece(5, 500, 1000, false, 0);
opt.setCutWidth(0);
opt.setRandomSeed(0.4);
const res = opt.optimizeGuillotine((a) => {});

this code give me this wrong result which is not guillotine!

{
"fitness": 1,
"stockPieces": [
{
"width": 1500,
"length": 3000,
"patternDirection": "none",
"cutPieces": [
{
"externalId": 2,
"x": 0,
"y": 0,
"width": 1000,
"length": 1000,
"patternDirection": "none",
"isRotated": false
},
{
"externalId": 3,
"x": 0,
"y": 1000,
"width": 500,
"length": 2000,
"patternDirection": "none",
"isRotated": false
},
{
"externalId": 5,
"x": 500,
"y": 1000,
"width": 500,
"length": 1000,
"patternDirection": "none",
"isRotated": false
},
{
"externalId": 4,
"x": 1000,
"y": 0,
"width": 500,
"length": 2000,
"patternDirection": "none",
"isRotated": false
},
{
"externalId": 1,
"x": 500,
"y": 2000,
"width": 1000,
"length": 1000,
"patternDirection": "none",
"isRotated": false
}
],
"wastePieces": []
}
]
}

Cut piece quantity

Following the semantics from StockPiece field quantity, I would like to request the addition of a similar field on CutPiece.

This would allow downstream code to specify multiples of the same cut piece without resorting to copying or iteration.

Present (0.3.0)

let left = CutPiece {
    external_id: Some(2),
    width: 500,
    length: 1250,
    can_rotate: false,
    pattern_direction: PatternDirection::ParallelToLength,
};

let right = CutPiece {
    external_id: Some(2),
    width: 500,
    length: 1250,
    can_rotate: false,
    pattern_direction: PatternDirection::ParallelToLength,
};

Future

let side = CutPiece {
    quantity: Some(2),
    external_id: Some(2),
    width: 500,
    length: 1250,
    can_rotate: false,
    pattern_direction: PatternDirection::ParallelToLength,
};

Unexpected behavior with multiple stock pieces and cut pieces.

Hi I'm having some strange behavior when adding multiple stock pieces and multiple cut pieces. The test below fails with this error:

NoFitForCutPiece(CutPiece { external_id: None, width: 64, length: 192, pattern_direction: None, can_rotate: false })

It seems like the optimizer is matching the a smaller cut piece to the larger stock piece, then failing with the larger one since it has no match. If you set the larger stock piece to quantity of 2 it will pass. Any ideas what's going on? I'll keep investigating...

#[test]
fn guillotine_stock_quantity_multiple_ok() {
    let solution = Optimizer::new()
        .add_stock_piece(StockPiece {
            width: 48,
            length: 96,
            pattern_direction: PatternDirection::None,
            price: 0,
            quantity: Some(2),
        })
        .add_stock_piece(StockPiece {
            width: 64,
            length: 192,
            pattern_direction: PatternDirection::None,
            price: 0,
            quantity: Some(1),
        })
        .add_cut_piece(CutPiece {
            external_id: None,
            width: 48,
            length: 96,
            pattern_direction: PatternDirection::None,
            can_rotate: false,
        })
        .add_cut_piece(CutPiece {
            external_id: None,
            width: 48,
            length: 96,
            pattern_direction: PatternDirection::None,
            can_rotate: false,
        })
        .add_cut_piece(CutPiece {
            external_id: None,
            width: 64,
            length: 192,
            pattern_direction: PatternDirection::None,
            can_rotate: false,
        })
        .set_cut_width(0)
        .set_random_seed(1)
        .optimize_guillotine(|_| {})
        .unwrap();

    sanity_check_solution(&solution, 3);
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.