easy-box-packer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +45 -0
  4. data/easy-box-packer.rb +143 -0
  5. metadata +81 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c160200ca99944f6560dca5948ce785bde238d87
4
+ data.tar.gz: 7d7cd114d8c6ebb74ad5fea4c5974210c2348143
5
+ SHA512:
6
+ metadata.gz: 4351a0c2f6a572d90e803e2218bc25f9d9e1635ca6d94f1b699b86656bdb34f77ecce108e76b97eea4dbebba49ba03741c36d64a1da10e39dea9628a10a38e00
7
+ data.tar.gz: 66c2d2b3a04c2b8e0b1ac1347f8fde974051bf75d06b79f2fac54642a9f76845c8d0af56c7ea7137c22bc73834c3fd8ec0746cd8e09c8a99b24920a55b4a91a6
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2017 Aloha Chen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # EasyBoxPacker
2
+
3
+ Ref from [box_packer](https://github.com/mushishi78/box_packer)
4
+
5
+ and inspired by [A genetic algorithm for the three-dimensional bin packing problem with heterogeneous bins](https://www.researchgate.net/publication/273121476_A_genetic_algorithm_for_the_three-dimensional_bin_packing_problem_with_heterogeneous_bins) and [A New Heuristic Algorithm for the 3D Bin Packing Problem](https://www.researchgate.net/publication/226249396_A_New_Heuristic_Algorithm_for_the_3D_Bin_Packing_Problem)
6
+
7
+ ## Installation
8
+
9
+ Add to gemfile:
10
+
11
+ ``` ruby
12
+ gem 'easy-box-packer'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ``` ruby
18
+ require 'easy-box-packer'
19
+
20
+ packings = BoxPacker.pack(
21
+ container: { dimensions: [15, 20, 13], weight_limit: 50 },
22
+ items: [
23
+ { dimensions: [2, 3, 5], weight: 47 },
24
+ { dimensions: [2, 3, 5], weight: 47 },
25
+ { dimensions: [3, 3, 1], weight: 24 },
26
+ { dimensions: [1, 1, 4], weight: 7 },
27
+ ]
28
+ )
29
+
30
+ packings.length # 3
31
+ packings[0][:weight] # 47
32
+ packings[0][:placements].length # 1
33
+ packings[0][:placements][0][:dimensions] # [2, 3, 5]
34
+ packings[0][:placements][0][:position] # [0, 0, 0]
35
+ packings[1][:weight] # 47
36
+ packings[1][:placements].length # 1
37
+ packings[1][:placements][0][:dimensions] # [2, 3, 5]
38
+ packings[1][:placements][0][:position] # [0, 0, 0]
39
+ packings[2][:weight] # 31
40
+ packings[2][:placements].length # 2
41
+ packings[2][:placements][0][:dimensions] # [1, 3, 3]
42
+ packings[2][:placements][0][:position] # [0, 0, 0]
43
+ packings[2][:placements][1][:dimensions] # [4, 1, 1]
44
+ packings[2][:placements][1][:position] # [3, 0, 0]
45
+ ```
@@ -0,0 +1,143 @@
1
+ module EasyBoxPacker
2
+ class << self
3
+ def pack(container:, items:)
4
+ packings = []
5
+ errors = []
6
+ # pack from biggest to smallest
7
+ items.sort_by { |h| h[:dimensions].sort }.reverse.each do |item|
8
+ # If the item is just too big for the container lets give up on this
9
+ if item[:weight].to_f > container[:weight_limit].to_f
10
+ errors << "Item: #{item} is too heavy for container"
11
+ next
12
+ end
13
+
14
+ # Need a bool so we can break out nested loops once it's been packed
15
+ item_has_been_packed = false
16
+
17
+ packings.each do |packing|
18
+ # If this packings going to be too big with this
19
+ # item as well then skip on to the next packing
20
+ next if packing[:weight].to_f + item[:weight].to_f > container[:weight_limit].to_f
21
+
22
+ # try minimum space first
23
+ packing[:spaces].sort_by { |h| h[:dimensions].sort }.each do |space|
24
+ # Try placing the item in this space,
25
+ # if it doesn't fit skip on the next space
26
+ next unless placement = place(item, space)
27
+
28
+ # Add the item to the packing and
29
+ # break up the surrounding spaces
30
+ packing[:placements] += [placement]
31
+ packing[:weight] += item[:weight].to_f
32
+ packing[:spaces] -= [space]
33
+ packing[:spaces] += break_up_space(space, placement)
34
+ item_has_been_packed = true
35
+ break
36
+ end
37
+ break if item_has_been_packed
38
+ end
39
+ next if item_has_been_packed
40
+
41
+ # Can't fit in any of the spaces for the current packings
42
+ # so lets try a new space the size of the container
43
+ space = {
44
+ dimensions: container[:dimensions].sort.reverse,
45
+ position: [0, 0, 0]
46
+ }
47
+ placement = place(item, space)
48
+
49
+ # If it can't be placed in this space, then it's just
50
+ # too big for the container and we should abandon hope
51
+ unless placement
52
+ errors << "Item: #{item} cannot be placed in container"
53
+ next
54
+ end
55
+
56
+ # Otherwise lets put the item in a new packing
57
+ # and break up the remaing free space around it
58
+ packings += [{
59
+ placements: [placement],
60
+ weight: item[:weight].to_f,
61
+ spaces: break_up_space(space, placement)
62
+ }]
63
+ end
64
+
65
+ { packings: packings, errors: errors }
66
+ end
67
+
68
+ def place(item, space)
69
+ item_width, item_height, item_length = item[:dimensions].sort.reverse
70
+
71
+ permutations = [
72
+ [item_width, item_height, item_length],
73
+ [item_width, item_length, item_height],
74
+ [item_height, item_width, item_length],
75
+ [item_height, item_length, item_width],
76
+ [item_length, item_width, item_height],
77
+ [item_length, item_height, item_width]
78
+ ]
79
+
80
+ possible_rotations_and_margins = []
81
+
82
+ permutations.each do |perm|
83
+ # Skip if the item does not fit with this orientation
84
+ next unless perm[0] <= space[:dimensions][0] &&
85
+ perm[1] <= space[:dimensions][1] &&
86
+ perm[2] <= space[:dimensions][2]
87
+
88
+ possible_margin = [space[:dimensions][0] - perm[0], space[:dimensions][1] - perm[1], space[:dimensions][2] - perm[2]]
89
+ possible_rotations_and_margins << { margin: possible_margin, rotation: perm }
90
+ end
91
+
92
+ # select rotation with smallest margin
93
+ final = possible_rotations_and_margins.sort_by { |a| a[:margin].sort }.first
94
+ return unless final
95
+ return {
96
+ dimensions: final[:rotation],
97
+ position: space[:position],
98
+ weight: item[:weight].to_f
99
+ }
100
+ end
101
+
102
+ def break_up_space(space, placement)
103
+ [
104
+ {
105
+ dimensions: [
106
+ space[:dimensions][0],
107
+ space[:dimensions][1],
108
+ space[:dimensions][2] - placement[:dimensions][2]
109
+ ],
110
+ position: [
111
+ space[:position][0],
112
+ space[:position][1],
113
+ space[:position][2] + placement[:dimensions][2]
114
+ ]
115
+ },
116
+ {
117
+ dimensions: [
118
+ space[:dimensions][0],
119
+ space[:dimensions][1] - placement[:dimensions][1],
120
+ placement[:dimensions][2]
121
+ ],
122
+ position: [
123
+ space[:position][0],
124
+ space[:position][1] + placement[:dimensions][1],
125
+ space[:position][2]
126
+ ]
127
+ },
128
+ {
129
+ dimensions: [
130
+ space[:dimensions][0] - placement[:dimensions][0],
131
+ placement[:dimensions][1],
132
+ placement[:dimensions][2]
133
+ ],
134
+ position: [
135
+ space[:position][0] + placement[:dimensions][0],
136
+ space[:position][1],
137
+ space[:position][2]
138
+ ]
139
+ }
140
+ ]
141
+ end
142
+ end
143
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy-box-packer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aloha Chen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: pry
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description:
48
+ email: y.alohac@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - LICENSE.txt
54
+ - README.md
55
+ - easy-box-packer.rb
56
+ homepage: https://github.com/alChaCC/easy-box-packer
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - "."
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.4.8
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: 3D bin-packing with weight limit using first-fit decreasing algorithm and
80
+ empty maximal spaces
81
+ test_files: []