box_packer 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +14 -1
- data/.rubocop.yml +4 -0
- data/README.md +57 -58
- data/Rakefile +1 -1
- data/box_packer.gemspec +9 -12
- data/lib/box_packer.rb +13 -3
- data/lib/box_packer/box.rb +29 -39
- data/lib/box_packer/builder.rb +14 -21
- data/lib/box_packer/container.rb +94 -107
- data/lib/box_packer/dimensions.rb +40 -42
- data/lib/box_packer/item.rb +27 -31
- data/lib/box_packer/packer.rb +52 -57
- data/lib/box_packer/packing.rb +30 -31
- data/lib/box_packer/position.rb +6 -8
- data/lib/box_packer/svg_exporter.rb +115 -119
- data/lib/box_packer/version.rb +1 -1
- data/spec/lib/box_spec.rb +51 -52
- data/spec/lib/container_spec.rb +88 -87
- data/spec/lib/dimensions_spec.rb +53 -51
- data/spec/lib/item_spec.rb +23 -26
- data/spec/lib/packer_spec.rb +69 -70
- data/spec/lib/packing_spec.rb +63 -60
- data/spec/spec_helper.rb +1 -14
- data/spec/support/matchers/yield_each_once.rb +38 -0
- metadata +27 -53
- data/spec/support/matchers/box_packer.rb +0 -40
data/lib/box_packer/version.rb
CHANGED
data/spec/lib/box_spec.rb
CHANGED
@@ -1,53 +1,52 @@
|
|
1
1
|
module BoxPacker
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
2
|
+
describe Box do
|
3
|
+
subject(:box) { Box.new(dimensions, position: position) }
|
4
|
+
let(:dimensions) { Dimensions[25, 30, 10] }
|
5
|
+
let(:position) { Position[10, 25, 5] }
|
6
|
+
let(:item) { Item.new(Dimensions[5, 2, 1]) }
|
7
|
+
|
8
|
+
context 'if no position is given' do
|
9
|
+
let(:position) { nil }
|
10
|
+
|
11
|
+
it 'defaults to origin position' do
|
12
|
+
expect(box.position).to eql(Position[0, 0, 0])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#orient!' do
|
17
|
+
before { box.orient! }
|
18
|
+
it { expect(box.dimensions).to eql(Dimensions[30, 25, 10]) }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#sub_boxes_args' do
|
22
|
+
let(:expected_args) do
|
23
|
+
[
|
24
|
+
[Dimensions[20, 30, 10], position: Position[15, 25, 5]],
|
25
|
+
[Dimensions[5, 28, 10], position: Position[10, 27, 5]],
|
26
|
+
[Dimensions[5, 2, 9], position: Position[10, 25, 6]]
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calculates the correct dimensions and positions' do
|
31
|
+
expect(box.send(:sub_boxes_args, item)).to eql(expected_args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#sub_boxes' do
|
36
|
+
it 'orders sub-boxes by volumes' do
|
37
|
+
sub_boxes = box.sub_boxes(item)
|
38
|
+
expect(sub_boxes[0].volume).to be >= (sub_boxes[1].volume)
|
39
|
+
expect(sub_boxes[1].volume).to be >= (sub_boxes[2].volume)
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with an item that reaches a side' do
|
43
|
+
let(:item) { Box.new(Dimensions[15, 2, 10]) }
|
44
|
+
|
45
|
+
it 'only returns 2 sub-boxes' do
|
46
|
+
sub_boxes = box.sub_boxes(item)
|
47
|
+
expect(sub_boxes.length).to eql(2)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/lib/container_spec.rb
CHANGED
@@ -1,88 +1,89 @@
|
|
1
1
|
module BoxPacker
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
2
|
+
describe Container do
|
3
|
+
subject(:container) { Container.new([25, 30, 15]) }
|
4
|
+
|
5
|
+
it { expect(container.packings).to eql(nil) }
|
6
|
+
|
7
|
+
context 'with items' do
|
8
|
+
let(:items) do
|
9
|
+
[
|
10
|
+
Item.new([15, 24, 8], weight: 25),
|
11
|
+
Item.new([2, 1, 2], weight: 6),
|
12
|
+
Item.new([9, 9, 10], weight: 30)
|
13
|
+
]
|
14
|
+
end
|
15
|
+
before { container.items = items }
|
16
|
+
|
17
|
+
context 'with container prepared' do
|
18
|
+
before do
|
19
|
+
container.send(:prepare_to_pack!)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#prepare_to_pack!' do
|
23
|
+
let(:expected_dimensions)do
|
24
|
+
[
|
25
|
+
Dimensions[24, 15, 8],
|
26
|
+
Dimensions[2, 2, 1],
|
27
|
+
Dimensions[10, 9, 9]
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
it { expect(container.items.map(&:dimensions)).to eql(expected_dimensions) }
|
32
|
+
it { expect(container.packings).to eql([]) }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with some items packed' do
|
36
|
+
before do
|
37
|
+
container.new_packing!
|
38
|
+
container.packing << items[1]
|
39
|
+
container.packing << items[2]
|
40
|
+
end
|
41
|
+
|
42
|
+
it { expect(container.packings.length).to eql(1) }
|
43
|
+
it { expect(container.packing).to match_array([items[1], items[2]]) }
|
44
|
+
|
45
|
+
describe '#new_packing!!' do
|
46
|
+
before { container.new_packing! }
|
47
|
+
it { expect(container.packings.length).to eql(2) }
|
48
|
+
it { expect(container.packing).to eql([]) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#packable?' do
|
54
|
+
context 'with no items' do
|
55
|
+
before { container.items = [] }
|
56
|
+
it { expect(container.send(:packable?)).to be(false) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with items that fit' do
|
60
|
+
it { expect(container.send(:packable?)).to be(true) }
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with an item to large' do
|
64
|
+
before { container.items[0].dimensions = Dimensions[26, 34, 8] }
|
65
|
+
it { expect(container.send(:packable?)).to be(false) }
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with a weight limit thats lighter than one of the items' do
|
69
|
+
before { container.weight_limit = 24 }
|
70
|
+
it { expect(container.send(:packable?)).to be(false) }
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'with a packings limit of one packing' do
|
74
|
+
before { container.packings_limit = 1 }
|
75
|
+
|
76
|
+
context 'with a weight limit thats lighter than items' do
|
77
|
+
before { container.weight_limit = 50 }
|
78
|
+
it { expect(container.send(:packable?)).to be(false) }
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'with a weight limit thats heavier than items' do
|
82
|
+
before { container.weight_limit = 70 }
|
83
|
+
it { expect(container.send(:packable?)).to be(true) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/lib/dimensions_spec.rb
CHANGED
@@ -1,52 +1,54 @@
|
|
1
|
+
require_relative '../support/matchers/yield_each_once'
|
2
|
+
|
1
3
|
module BoxPacker
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
4
|
+
describe Dimensions do
|
5
|
+
include BoxPacker::Matchers
|
6
|
+
subject(:dimensions) { Dimensions[2, 10, 3] }
|
7
|
+
|
8
|
+
describe '#volume' do
|
9
|
+
it { expect(dimensions.volume).to eql(60) }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#>=' do
|
13
|
+
context 'when compared with Dimensions that are all bigger' do
|
14
|
+
let(:other_dimensions) { Dimensions[1, 5, 2] }
|
15
|
+
it { expect(dimensions >= other_dimensions).to be(true) }
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when compared with Dimensions where some are bigger and some are equal' do
|
19
|
+
let(:other_dimensions) { Dimensions[2, 5, 3] }
|
20
|
+
it { expect(dimensions >= other_dimensions).to be(true) }
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when compared with Dimensions that are all equal' do
|
24
|
+
let(:other_dimensions) { Dimensions[2, 10, 3] }
|
25
|
+
it { expect(dimensions >= other_dimensions).to be(true) }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when compared with Dimensions where some are bigger and some are smaller' do
|
29
|
+
let(:other_dimensions) { Dimensions[5, 5, 1] }
|
30
|
+
it { expect(dimensions >= other_dimensions).to be(false) }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when compared with Dimensions where none are bigger' do
|
34
|
+
let(:other_dimensions) { Dimensions[5, 15, 11] }
|
35
|
+
it { expect(dimensions >= other_dimensions).to be(false) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#each_rotation' do
|
40
|
+
let(:rotations)do
|
41
|
+
[
|
42
|
+
Dimensions[2, 10, 3],
|
43
|
+
Dimensions[2, 3, 10],
|
44
|
+
Dimensions[10, 2, 3],
|
45
|
+
Dimensions[10, 3, 2],
|
46
|
+
Dimensions[3, 10, 2],
|
47
|
+
Dimensions[3, 2, 10]
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
it { expect { |b| dimensions.each_rotation(&b) }.to yield_each_once(rotations) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/spec/lib/item_spec.rb
CHANGED
@@ -1,30 +1,27 @@
|
|
1
1
|
module BoxPacker
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
describe Item do
|
3
|
+
subject(:item) { Item.new(dimensions) }
|
4
|
+
let(:dimensions) { [12, 5, 7] }
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
let(:rotated_dimensions) { Dimensions[5, 12, 7] }
|
11
|
-
|
12
|
-
it 'fits and orientation is rotated to fit' do
|
13
|
-
expect(item.fit_into?(box)).to be(true)
|
14
|
-
expect(item.dimensions).to eql(rotated_dimensions)
|
15
|
-
end
|
16
|
-
end
|
6
|
+
describe '#fit_into' do
|
7
|
+
context 'when box is larger than box' do
|
8
|
+
let(:box) { Box.new(Dimensions[6, 15, 9]) }
|
9
|
+
let(:rotated_dimensions) { Dimensions[5, 12, 7] }
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
expect(item.dimensions).to eql(Dimensions[*dimensions])
|
24
|
-
end
|
25
|
-
end
|
11
|
+
it 'fits and orientation is rotated to fit' do
|
12
|
+
expect(item.fit_into?(box)).to be(true)
|
13
|
+
expect(item.dimensions).to eql(rotated_dimensions)
|
14
|
+
end
|
15
|
+
end
|
26
16
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
17
|
+
context 'when other box has a smaller side than box' do
|
18
|
+
let(:box) { Box.new(Dimensions[11, 6, 7]) }
|
19
|
+
|
20
|
+
it 'does not fit and orientation is unchanged' do
|
21
|
+
expect(item.fit_into?(box)).to be(false)
|
22
|
+
expect(item.dimensions).to eql(Dimensions[*dimensions])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/spec/lib/packer_spec.rb
CHANGED
@@ -1,81 +1,80 @@
|
|
1
1
|
module BoxPacker
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
2
|
+
describe Packer do
|
3
|
+
subject(:container) { Container.new(dimensions) }
|
4
|
+
let(:dimensions) { [10, 15, 5] }
|
5
|
+
let(:opts) { nil }
|
6
|
+
let(:items)do
|
7
|
+
[
|
8
|
+
Item.new([1, 1, 1], weight: 1),
|
9
|
+
Item.new([3, 1, 1], weight: 6),
|
10
|
+
Item.new([5, 5, 1], weight: 15),
|
11
|
+
Item.new([5, 5, 5], weight: 20)
|
12
|
+
]
|
13
|
+
end
|
12
14
|
|
13
|
-
|
15
|
+
describe '#pack' do
|
16
|
+
context 'with items that fit exactly in one packing' do
|
17
|
+
before do
|
18
|
+
20.times { container.items << items[0].dup }
|
19
|
+
10.times { container.items << items[1].dup }
|
20
|
+
8.times { container.items << items[2].dup }
|
21
|
+
4.times { container.items << items[3].dup }
|
22
|
+
end
|
14
23
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
4.times { container.items << items[3].dup }
|
21
|
-
end
|
22
|
-
|
23
|
-
it do
|
24
|
-
expect(container.pack!).to eql(1)
|
25
|
-
expect(container.packed_successfully).to be(true)
|
26
|
-
end
|
27
|
-
end
|
24
|
+
it do
|
25
|
+
expect(container.pack!).to eql(1)
|
26
|
+
expect(container.packed_successfully).to be(true)
|
27
|
+
end
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
context 'with items that fit exactly in three packings' do
|
31
|
+
before do
|
32
|
+
35.times { container.items << items[0].dup }
|
33
|
+
30.times { container.items << items[1].dup }
|
34
|
+
10.times { container.items << items[2].dup }
|
35
|
+
15.times { container.items << items[3].dup }
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
it do
|
39
|
+
expect(container.pack!).to eql(3)
|
40
|
+
expect(container.packed_successfully).to be(true)
|
41
|
+
end
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
context 'with a packing limit of one and too many items' do
|
45
|
+
before do
|
46
|
+
container.packings_limit = 1
|
47
|
+
35.times { container.items << items[0].dup }
|
48
|
+
30.times { container.items << items[1].dup }
|
49
|
+
10.times { container.items << items[2].dup }
|
50
|
+
15.times { container.items << items[3].dup }
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
it do
|
54
|
+
expect(container.pack!).to be(false)
|
55
|
+
expect(container.packed_successfully).to be(false)
|
56
|
+
end
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
context 'with random container and random items' do
|
60
|
+
let(:dimensions) { [x, y, z] }
|
61
|
+
let(:x) { rand(1..75) }
|
62
|
+
let(:y) { rand(1..75) }
|
63
|
+
let(:z) { rand(1..75) }
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
let(:items) do
|
66
|
+
(1..rand(1..100)).map do
|
67
|
+
Item.new([x / rand(1..5), y / rand(1..5), z / rand(1..5)])
|
68
|
+
end
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
before do
|
72
|
+
container.items = items
|
73
|
+
container.pack!
|
74
|
+
end
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
76
|
+
it { expect(container.packed_successfully).to be(true) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|