box_packer 1.2.3 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 577f9d185a95ed861e725040c6af624d2470fbea
4
- data.tar.gz: e1959d0f2230fd26b464b5340d31cd45ea29c3b2
3
+ metadata.gz: b379d4407ae68ae0444cb2b5c2718f4ecdda9e5a
4
+ data.tar.gz: 883feddbe4f741e7d2f6cba5547635d467f54d80
5
5
  SHA512:
6
- metadata.gz: 708d9ad7db2ca97d46c275e9545ff67e1aadd6788fc439dff8a78acc46d8b334a95d1c8d27a686b61357093a8a11bcf1f41b9c1bacc28ab72aa9d1933a88bcb6
7
- data.tar.gz: 704c4c92f46af99ecf269347ecef930f41368922642c3447aed04e2dfdf0e77330a356da14bdcffd2b21eaa5cbb98bd5d985abb1e307849d7e926d3342983626
6
+ metadata.gz: 1711debc5d423788042c3b9ec7c9cde8e66431b4ddc18b1a31c85ee7bff0392787b097e2f225ab8924a5b5013a101a5229c1fdeae40e74d6c391dabd88e8c766
7
+ data.tar.gz: cd1147664c5a9d1091fa958a466f3f5d11a2c0daad15cf5f5aae186c140dc4ecfc683c2ecde4ac8c0ca7a218b5b165a9b42e4d9b2b5a2e639f86c3a11074eb78
@@ -1,22 +1,22 @@
1
- Copyright (c) 2014 Max White
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.
1
+ Copyright (c) 2014 Max White
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 CHANGED
@@ -1,115 +1,50 @@
1
- BoxPacker
2
- =========
1
+ # BoxPacker
3
2
 
4
- [![Gem Version](https://badge.fury.io/rb/box_packer.svg)](http://badge.fury.io/rb/box_packer)
3
+ First fit heuristic algorithm for 3D bin-packing with weight limit.
5
4
 
6
- A heuristic first-fit 3D bin packing algorithm with optional weight and bin limits.
5
+ ## Version 2
7
6
 
8
- Installation
9
- ------------
7
+ For version 2 this entire solution has been rewritten with an emphasis on making a much simpler
8
+ and easier to understand implementation. However, I haven't gone to the trouble of reproducing
9
+ all the previous functionality, so if you still need that, it can be found here:
10
+ [1.2.4](https://github.com/mushishi78/box_packer/tree/1.2.4)
10
11
 
11
- Install gem:
12
+ ## Installation
12
13
 
13
- ``` console
14
- gem install 'box_packer'
15
- ```
16
-
17
- Or add to gemfile:
14
+ Add to gemfile:
18
15
 
19
16
  ``` ruby
20
17
  gem 'box_packer'
21
18
  ```
22
19
 
23
- Usage
24
- -----
20
+ ## Usage
25
21
 
26
22
  ``` ruby
27
23
  require 'box_packer'
28
24
 
29
- BoxPacker.container [3, 6, 7] do
30
- add_item [1,3,5]
31
- add_item [4,3,5]
32
- add_item [3,5,5]
33
- pack! # 2
34
-
35
- puts packed_successfully # true
36
- puts packings.count # 2
37
- puts packings[0].include? items[1] # false
38
- puts packings[0][1].position # (5,0,0)
39
-
40
- puts self # |Container| 7x6x3
41
- # | Packing| Remaining Volume:36
42
- # | Item| 5x5x3 (0,0,0) Volume:75
43
- # | Item| 1x5x3 (5,0,0) Volume:15
44
- # | Packing| Remaining Volume:66
45
- # | Item| 5x4x3 (0,0,0) Volume:60
46
- end
47
- ```
48
-
49
- With optional labels, weights, quantity and packings limit:
50
-
51
- ``` ruby
52
- BoxPacker.container [15, 20, 13], label: 'Parcel', weight_limit: 50, packings_limit: 3 do
53
- add_item [2, 3, 5], label: 'Shoes', weight: 47, quantity: 2
54
- add_item [3, 3, 1], label: 'Watch', weight: 24
55
- add_item [1, 1, 4], label: 'Bag', weight: 7
56
- pack! # 3
57
-
58
- puts self # |Container| Parcel 20x15x13 Weight Limit:50 Packings Limit:3
59
- # | Packing| Remaining Volume:3870 Remaining Weight:3
60
- # | Item| Shoes 5x3x2 (0,0,0) Volume:30 Weight:47
61
- # | Packing| Remaining Volume:3870 Remaining Weight:3
62
- # | Item| Shoes 5x3x2 (0,0,0) Volume:30 Weight:47
63
- # | Packing| Remaining Volume:3887 Remaining Weight:19
64
- # | Item| Watch 3x3x1 (0,0,0) Volume:9 Weight:24
65
- # | Item| Bag 4x1x1 (3,0,0) Volume:4 Weight:7
66
- end
67
- ```
68
-
69
- Alternative builder API:
70
-
71
- ``` ruby
72
- BoxPacker.builder do |b|
73
- c1 = b.container [10,5,11]
74
- c2 = b.container [17,23,14]
75
-
76
- c1.items = [b.item([1,1,4]), b.item([4,6,7]), b.item([5,8,10])]
77
- c2.items = c1.items
78
-
79
- c1.pack! # 2
80
- c2.pack! # 1
81
-
82
- puts c1 # |Container| 11x10x5
83
- # | Packing| Remaining Volume:146
84
- # | Item| 10x8x5 (0,0,0) Volume:400
85
- # | Item| 4x1x1 (0,8,0) Volume:4
86
- # | Packing| Remaining Volume:382
87
- # | Item| 7x6x4 (10,0,0) Volume:168
88
-
89
- puts c2 # |Container| 23x17x14
90
- # | Packing| Remaining Volume:4902
91
- # | Item| 10x8x5 (0,0,0) Volume:400
92
- # | Item| 7x6x4 (10,0,0) Volume:168
93
- # | Item| 4x1x1 (17,0,0) Volume:4
94
-
95
- end
25
+ packings = BoxPacker.pack(
26
+ container: { dimensions: [15, 20, 13], weight_limit: 50 },
27
+ items: [
28
+ { dimensions: [2, 3, 5], weight: 47 },
29
+ { dimensions: [2, 3, 5], weight: 47 },
30
+ { dimensions: [3, 3, 1], weight: 24 },
31
+ { dimensions: [1, 1, 4], weight: 7 },
32
+ ]
33
+ )
34
+
35
+ packings.length # 3
36
+ packings[0][:weight] # 47
37
+ packings[0][:placements].length # 1
38
+ packings[0][:placements][0][:dimensions] # [5, 3, 2]
39
+ packings[0][:placements][0][:position] # [0, 0, 0]
40
+ packings[1][:weight] # 47
41
+ packings[1][:placements].length # 1
42
+ packings[1][:placements][0][:dimensions] # [5, 3, 2]
43
+ packings[1][:placements][0][:position] # [0, 0, 0]
44
+ packings[2][:weight] # 31
45
+ packings[2][:placements].length # 2
46
+ packings[2][:placements][0][:dimensions] # [3, 3, 1]
47
+ packings[2][:placements][0][:position] # [0, 0, 0]
48
+ packings[2][:placements][1][:dimensions] # [4, 1, 1]
49
+ packings[2][:placements][1][:position] # [3, 0, 0]
96
50
  ```
97
-
98
- Export SVG
99
- ----------
100
-
101
- ``` ruby
102
- BoxPacker.container [3, 4, 2] do
103
- add_item [1,3,2], label: 'Bag', colour: 'red'
104
- add_item [3,3,1], label: 'Hat', colour: 'blue'
105
- add_item [1,2,2], label: 'Shoes', colour: 'green'
106
- add_item [3,1,1], label: 'Slipper', colour: 'purple'
107
- add_item [2,1,1], label: 'Dragon', colour: 'orange'
108
- pack!
109
- draw!('examples/example', scale_longest_side_to: 500, margin: 15)
110
- end
111
- ```
112
-
113
- Output:
114
-
115
- ![SVG Output](https://rawgit.com/mushishi78/box_packer/master/examples/example1.svg)
@@ -0,0 +1,127 @@
1
+ module BoxPacker
2
+ class << self
3
+ def pack(container:, items:)
4
+ packings = []
5
+
6
+ items.each do |item|
7
+ # If the item is just too big for the container lets give up on this
8
+ raise 'Item is too heavy for container' if item[:weight] > container[:weight_limit]
9
+
10
+ # Need a bool so we can break out nested loops once it's been packed
11
+ item_has_been_packed = false
12
+
13
+ packings.each do |packing|
14
+ # If this packings going to be too big with this
15
+ # item as well then skip on to the next packing
16
+ next if packing[:weight] + item[:weight] > container[:weight_limit]
17
+
18
+ packing[:spaces].each do |space|
19
+ # Try placing the item in this space,
20
+ # if it doesn't fit skip on the next space
21
+ next unless placement = place(item, space)
22
+
23
+ # Add the item to the packing and
24
+ # break up the surrounding spaces
25
+ packing[:placements] += [placement]
26
+ packing[:weight] += item[:weight]
27
+ packing[:spaces] -= [space]
28
+ packing[:spaces] += break_up_space(space, placement)
29
+ item_has_been_packed = true
30
+ break
31
+ end
32
+ break if item_has_been_packed
33
+ end
34
+ break if item_has_been_packed
35
+
36
+ # Can't fit in any of the spaces for the current packings
37
+ # so lets try a new space the size of the container
38
+ space = {
39
+ dimensions: container[:dimensions].sort.reverse,
40
+ position: [0, 0, 0]
41
+ }
42
+ placement = place(item, space)
43
+
44
+ # If it can't be placed in this space, then it's just
45
+ # too big for the container and we should abandon hope
46
+ raise 'Item cannot be placed in container' unless placement
47
+
48
+ # Otherwise lets put the item in a new packing
49
+ # and break up the remaing free space around it
50
+ packings += [{
51
+ placements: [placement],
52
+ weight: item[:weight],
53
+ spaces: break_up_space(space, placement)
54
+ }]
55
+ end
56
+
57
+ packings
58
+ end
59
+
60
+ def place(item, space)
61
+ item_width, item_height, item_length = item[:dimensions].sort.reverse
62
+
63
+ permutations = [
64
+ [item_width, item_height, item_length],
65
+ [item_width, item_length, item_height],
66
+ [item_height, item_width, item_length],
67
+ [item_height, item_length, item_width],
68
+ [item_length, item_width, item_height],
69
+ [item_length, item_height, item_width]
70
+ ]
71
+
72
+ permutations.each do |perm|
73
+ # Skip if the item does not fit with this orientation
74
+ next unless perm[0] <= space[:dimensions][0] &&
75
+ perm[1] <= space[:dimensions][1] &&
76
+ perm[2] <= space[:dimensions][2]
77
+
78
+ return {
79
+ dimensions: perm,
80
+ position: space[:position],
81
+ weight: item[:weight]
82
+ }
83
+ end
84
+ end
85
+
86
+ def break_up_space(space, placement)
87
+ [
88
+ {
89
+ dimensions: [
90
+ space[:dimensions][0],
91
+ space[:dimensions][1],
92
+ space[:dimensions][2] - placement[:dimensions][0]
93
+ ],
94
+ position: [
95
+ space[:position][0] + placement[:dimensions][0],
96
+ space[:position][1],
97
+ space[:position][2]
98
+ ]
99
+ },
100
+ {
101
+ dimensions: [
102
+ placement[:dimensions][0],
103
+ space[:dimensions][1],
104
+ space[:dimensions][2] - placement[:dimensions][1]
105
+ ],
106
+ position: [
107
+ space[:position][0],
108
+ space[:position][1] + placement[:dimensions][1],
109
+ space[:position][2]
110
+ ]
111
+ },
112
+ {
113
+ dimensions: [
114
+ placement[:dimensions][0],
115
+ placement[:dimensions][1],
116
+ space[:dimensions][2] - placement[:dimensions][2]
117
+ ],
118
+ position: [
119
+ space[:position][0],
120
+ space[:position][1],
121
+ space[:position][2] + placement[:dimensions][2]
122
+ ]
123
+ }
124
+ ]
125
+ end
126
+ end
127
+ end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: box_packer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max White
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-23 00:00:00.000000000 Z
11
+ date: 2016-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: rasem
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.6'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.6'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rspec
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -52,17 +38,7 @@ extra_rdoc_files: []
52
38
  files:
53
39
  - LICENSE.txt
54
40
  - README.md
55
- - lib/box_packer.rb
56
- - lib/box_packer/box.rb
57
- - lib/box_packer/builder.rb
58
- - lib/box_packer/container.rb
59
- - lib/box_packer/dimensions.rb
60
- - lib/box_packer/item.rb
61
- - lib/box_packer/packer.rb
62
- - lib/box_packer/packing.rb
63
- - lib/box_packer/position.rb
64
- - lib/box_packer/svg_exporter.rb
65
- - lib/box_packer/vector.rb
41
+ - box_packer.rb
66
42
  homepage: https://github.com/mushishi78/box_packer
67
43
  licenses:
68
44
  - MIT
@@ -70,7 +46,7 @@ metadata: {}
70
46
  post_install_message:
71
47
  rdoc_options: []
72
48
  require_paths:
73
- - lib
49
+ - "."
74
50
  required_ruby_version: !ruby/object:Gem::Requirement
75
51
  requirements:
76
52
  - - ">="
@@ -83,8 +59,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
59
  version: '0'
84
60
  requirements: []
85
61
  rubyforge_project:
86
- rubygems_version: 2.2.2
62
+ rubygems_version: 2.6.0
87
63
  signing_key:
88
64
  specification_version: 4
89
- summary: 3D bin-packing algorithm with optional weight and bin limits.
65
+ summary: First fit heuristic algorithm for 3D bin-packing with weight limit.
90
66
  test_files: []
@@ -1,2 +0,0 @@
1
- require 'box_packer/container'
2
- require 'box_packer/builder'
@@ -1,39 +0,0 @@
1
- require 'forwardable'
2
- require_relative 'position'
3
- require_relative 'dimensions'
4
-
5
- module BoxPacker
6
- class Box
7
- extend Forwardable
8
-
9
- def initialize(dimensions, opts = {})
10
- @dimensions = dimensions
11
- @position = opts[:position] || Position[0, 0, 0]
12
- end
13
-
14
- def_delegators :dimensions, :volume, :each_rotation, :width, :height, :depth
15
- attr_accessor :dimensions, :position
16
-
17
- def orient!
18
- @dimensions = Dimensions[*dimensions.to_a.sort!.reverse!]
19
- end
20
-
21
- def >=(other)
22
- dimensions >= other.dimensions
23
- end
24
-
25
- def sub_boxes(item)
26
- sub_boxes = sub_boxes_args(item).select { |(d, _)| d.volume > 0 }
27
- sub_boxes.map! { |args| Box.new(*args) }
28
- sub_boxes.sort_by!(&:volume).reverse!
29
- end
30
-
31
- private
32
-
33
- def sub_boxes_args(item)
34
- [[width + height + depth - item.width, position: position + item.width],
35
- [item.width + height + depth - item.height, position: position + item.height],
36
- [item.width + item.height + depth - item.depth, position: position + item.depth]]
37
- end
38
- end
39
- end
@@ -1,18 +0,0 @@
1
- require_relative 'container'
2
- require_relative 'item'
3
-
4
- module BoxPacker
5
- def self.builder(&b)
6
- b.call(Builder.new) if block_given?
7
- end
8
-
9
- class Builder
10
- def container(*args, &b)
11
- Container.new(*args, &b)
12
- end
13
-
14
- def item(*args)
15
- Item.new(*args)
16
- end
17
- end
18
- end
@@ -1,102 +0,0 @@
1
- require_relative 'dimensions'
2
- require_relative 'item'
3
- require_relative 'packer'
4
- require_relative 'packing'
5
- require_relative 'svg_exporter'
6
- require_relative 'box'
7
-
8
- module BoxPacker
9
- def self.container(*args, &b)
10
- Container.new(*args, &b)
11
- end
12
-
13
- class Container < Box
14
- attr_accessor :label, :weight_limit, :packings_limit
15
- attr_reader :items, :packing, :packings, :packed_successfully
16
-
17
- def initialize(dimensions, opts = {}, &b)
18
- super(Dimensions[*dimensions])
19
- @label = opts[:label]
20
- @weight_limit = opts[:weight_limit]
21
- @packings_limit = opts[:packings_limit]
22
- @items = opts[:items] || []
23
- orient!
24
- instance_exec(&b) if b
25
- end
26
-
27
- def add_item(dimensions, opts = {})
28
- quantity = opts.delete(:quantity) || 1
29
- quantity.times do
30
- items << Item.new(dimensions, opts)
31
- end
32
- end
33
-
34
- def <<(item)
35
- items << item.dup
36
- end
37
-
38
- def items=(new_items)
39
- @items = new_items.map(&:dup)
40
- end
41
-
42
- def pack!
43
- prepare_to_pack!
44
- return unless packable?
45
- if @packed_successfully = Packer.pack(self)
46
- packings.count
47
- else
48
- @packings = []
49
- false
50
- end
51
- end
52
-
53
- def new_packing!
54
- @packing = Packing.new(volume, weight_limit)
55
- @packings << @packing
56
- end
57
-
58
- def to_s
59
- s = "\n|Container|"
60
- s << " #{label}" if label
61
- s << " #{dimensions}"
62
- s << " Weight Limit:#{weight_limit}" if weight_limit
63
- s << " Packings Limit:#{packings_limit}" if packings_limit
64
- s << "\n"
65
- s << (packed_successfully ? packings.map(&:to_s).join : '| | Did Not Pack!')
66
- end
67
-
68
- def draw!(filename, opts = {})
69
- exporter = SVGExporter.new(self, opts)
70
- exporter.draw
71
- exporter.save(filename)
72
- end
73
-
74
- private
75
-
76
- def packable?
77
- return false if items.empty?
78
- total_weight = 0
79
-
80
- items.each do |item|
81
- if weight_limit && item.weight
82
- return false if item.weight > weight_limit
83
- total_weight += item.weight
84
- end
85
-
86
- return false unless self >= item
87
- end
88
-
89
- if weight_limit && packings_limit
90
- return total_weight <= weight_limit * packings_limit
91
- end
92
-
93
- true
94
- end
95
-
96
- def prepare_to_pack!
97
- items.each(&:orient!)
98
- @packings = []
99
- @packed_successfully = false
100
- end
101
- end
102
- end
@@ -1,26 +0,0 @@
1
- require_relative 'vector'
2
-
3
- module BoxPacker
4
- class Dimensions < Vector
5
- def volume
6
- @volume ||= to_a.reduce(&:*)
7
- end
8
-
9
- def >=(other)
10
- zip_map(other, :>=).reduce(&:&)
11
- end
12
-
13
- def each_rotation
14
- yield Dimensions[x, y, z]
15
- yield Dimensions[x, z, y]
16
- yield Dimensions[z, x, y]
17
- yield Dimensions[z, y, x]
18
- yield Dimensions[y, x, z]
19
- yield Dimensions[y, z, x]
20
- end
21
-
22
- def to_s
23
- to_a.join('x')
24
- end
25
- end
26
- end
@@ -1,34 +0,0 @@
1
- require_relative 'box'
2
- require_relative 'dimensions'
3
-
4
- module BoxPacker
5
- class Item < Box
6
- attr_accessor :label, :weight
7
- attr_reader :colour
8
-
9
- def initialize(dimensions, opts = {})
10
- super(Dimensions[*dimensions])
11
- @label = opts[:label].to_s
12
- @weight = opts[:weight]
13
- @colour = opts[:colour] || '%06x' % (rand * 0xffffff)
14
- end
15
-
16
- def rotate_to_fit_into(box)
17
- each_rotation do |rotation|
18
- if box.dimensions >= rotation
19
- @dimensions = rotation
20
- return true
21
- end
22
- end
23
- false
24
- end
25
-
26
- def to_s
27
- s = '| Item|'
28
- s << " #{label}" if label
29
- s << " #{dimensions} #{position} Volume:#{volume}"
30
- s << " Weight:#{weight}" if weight
31
- s << "\n"
32
- end
33
- end
34
- end
@@ -1,63 +0,0 @@
1
- module BoxPacker
2
- class Packer
3
- extend Forwardable
4
-
5
- def self.pack(container)
6
- new(container).pack
7
- end
8
-
9
- def initialize(container)
10
- @container = container
11
- end
12
-
13
- def_delegators :container, :new_packing!, :packings_limit, :packings, :packing
14
-
15
- def pack
16
- @items = container.items.sort_by!(&:volume).reverse!
17
-
18
- until too_many_packings?
19
- new_packing!
20
- pack_box(@items, container)
21
- return true if @items.empty?
22
- end
23
- false
24
- end
25
-
26
- private
27
-
28
- attr_reader :container
29
-
30
- def too_many_packings?
31
- packings.count >= packings_limit if packings_limit
32
- end
33
-
34
- def pack_box(possible_items, box)
35
- possible_items = possible_items.dup
36
- until possible_items.empty?
37
- item = possible_items.shift
38
- next unless item.rotate_to_fit_into(box)
39
-
40
- pack_item!(item, possible_items, box)
41
- break if possible_items.empty?
42
-
43
- box.sub_boxes(item).each do |sub_box|
44
- purge!(possible_items)
45
- pack_box(possible_items, sub_box)
46
- end
47
- break
48
- end
49
- end
50
-
51
- def pack_item!(item, possible_items, box)
52
- item.position = box.position
53
- possible_items.delete(item)
54
- packing << @items.delete(item)
55
- end
56
-
57
- def purge!(possible_items)
58
- possible_items.keep_if do |item|
59
- packing.fit?(item)
60
- end
61
- end
62
- end
63
- end
@@ -1,39 +0,0 @@
1
- require 'delegate'
2
-
3
- module BoxPacker
4
- class Packing < SimpleDelegator
5
- attr_reader :remaining_weight, :remaining_volume
6
-
7
- def initialize(total_volume, total_weight)
8
- super([])
9
- @remaining_volume = total_volume
10
- @remaining_weight = total_weight
11
- end
12
-
13
- def <<(item)
14
- @remaining_volume -= item.volume
15
- @remaining_weight -= item.weight if weight?(item)
16
- super
17
- end
18
-
19
- def fit?(item)
20
- return false if include?(item)
21
- return false if remaining_volume < item.volume
22
- return false if weight?(item) && remaining_weight < item.weight
23
- true
24
- end
25
-
26
- def to_s
27
- s = "| Packing| Remaining Volume:#{remaining_volume}"
28
- s << " Remaining Weight:#{remaining_weight}" if remaining_weight
29
- s << "\n"
30
- s << map(&:to_s).join
31
- end
32
-
33
- private
34
-
35
- def weight?(item)
36
- remaining_weight && item.weight
37
- end
38
- end
39
- end
@@ -1,9 +0,0 @@
1
- require_relative 'vector'
2
-
3
- module BoxPacker
4
- class Position < Vector
5
- def to_s
6
- "(#{to_a.join(',')})"
7
- end
8
- end
9
- end
@@ -1,117 +0,0 @@
1
- require 'rasem'
2
-
3
- module BoxPacker
4
- class SVGExporter
5
- def initialize(container, opts = {})
6
- @container = container
7
- @images = []
8
- @margin = opts[:margin] || 10
9
-
10
- dimensions = container.dimensions.to_a
11
- longest_side = dimensions.max
12
- sides_total = dimensions.reduce(&:+)
13
- scale_longest_side_to = opts[:scale_longest_side_to] || 400
14
-
15
- @scale = scale_longest_side_to / longest_side.to_f
16
- @image_width = (longest_side * scale * 2) + (margin * 3)
17
- @image_height = (sides_total * scale) + (margin * 4)
18
- end
19
-
20
- def save(filename)
21
- images.each_with_index do |image, i|
22
- image.close
23
-
24
- File.open("#{filename}#{i + 1}.svg", 'w') do |f|
25
- f << image.output
26
- end
27
- end
28
- end
29
-
30
- def draw
31
- container.packings.each do |packing|
32
- Face.reset(margin, scale, container.dimensions)
33
- new_image
34
- 6.times do
35
- face = Face.new(packing)
36
- image.rectangle(*face.outline, stroke: 'black', stroke_width: 1, fill: 'white')
37
- face.rectangles_and_labels.each do |h|
38
- image.rectangle(*h[:rectangle])
39
- image.text(*h[:label])
40
- end
41
- end
42
- end
43
- end
44
-
45
- private
46
-
47
- attr_reader :container, :scale, :margin, :images, :image, :image_width, :image_height
48
-
49
- def new_image
50
- @image = Rasem::SVGImage.new(image_width, image_height)
51
- images << image
52
- end
53
-
54
- class Face
55
- attr_reader :width, :height, :axes
56
-
57
- def self.reset(margin, scale, container_dimensions)
58
- @@coords_mapping = [0, 1, 2]
59
- @@front = true
60
- @@margin = margin
61
- @@axes = [margin, margin]
62
- @@scale = scale
63
- @@container_dimensions = container_dimensions
64
- end
65
-
66
- def iterate_class_variables
67
- if front
68
- @@axes[0] = width + @@margin * 2
69
- else
70
- @@coords_mapping.rotate!
71
- @@axes[0] = @@margin
72
- @@axes[1] += height + @@margin
73
- end
74
- @@front = !@@front
75
- end
76
-
77
- def initialize(packing)
78
- @i, @j, @k = @@coords_mapping.dup
79
- @front = @@front
80
- @axes = @@axes.dup
81
- @width = @@container_dimensions[i] * @@scale
82
- @height = @@container_dimensions[j] * @@scale
83
- iterate_class_variables
84
- @items = sorted_items(packing)
85
- end
86
-
87
- def outline
88
- @axes + [width, height]
89
- end
90
-
91
- def rectangles_and_labels
92
- items.map do |item|
93
- x = axes[0] + item.position[i] * @@scale
94
- y = axes[1] + item.position[j] * @@scale
95
- width = item.dimensions[i] * @@scale
96
- height = item.dimensions[j] * @@scale
97
- label_x = x + width / 2 - item.label.length
98
- label_y = y + height / 2
99
- {
100
- rectangle: [x, y, width, height, fill: item.colour],
101
- label: [label_x, label_y, item.label]
102
- }
103
- end
104
- end
105
-
106
- private
107
-
108
- attr_reader :packing, :i, :j, :k, :front, :items
109
-
110
- def sorted_items(packing)
111
- items = packing.sort_by { |i| i.position[k] }
112
- items.reverse! unless front
113
- items
114
- end
115
- end
116
- end
117
- end
@@ -1,51 +0,0 @@
1
- module BoxPacker
2
- class Vector
3
- def self.[](*args)
4
- new(*args)
5
- end
6
-
7
- def initialize(x, y, z)
8
- @x, @y, @z = x, y, z
9
- end
10
-
11
- attr_reader :x, :y, :z
12
-
13
- def +(other)
14
- self.class.new(*zip_map(other, :+))
15
- end
16
-
17
- def -(other)
18
- self.class.new(*zip_map(other, :-))
19
- end
20
-
21
- def width
22
- self.class.new(x, 0, 0)
23
- end
24
-
25
- def height
26
- self.class.new(0, y, 0)
27
- end
28
-
29
- def depth
30
- self.class.new(0, 0, z)
31
- end
32
-
33
- def to_a
34
- [x, y, z]
35
- end
36
-
37
- def ==(other)
38
- zip_map(other, :==).reduce(&:&)
39
- end
40
-
41
- def eql?(other)
42
- zip_map(other, :eql?).reduce(&:&)
43
- end
44
-
45
- private
46
-
47
- def zip_map(other, method)
48
- to_a.zip(other.to_a).map { |a, b| a.send(method, b) }
49
- end
50
- end
51
- end