box_packer 1.2.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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