easy-box-packer 0.0.1

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.
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: []