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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eba591c80a0ed22630cab9bb8ae6f6c4f61f8a4e
4
- data.tar.gz: a078c724e6cb21db22d3d5937a635bf8bd5cee28
3
+ metadata.gz: cb419643a33b63ce8d279f9baafa0c2e4c010300
4
+ data.tar.gz: a5174fcc00bc4ccb58542182ac40a7aceef95f14
5
5
  SHA512:
6
- metadata.gz: 4db6ea115a0c889b38f6308c3ac6904c22afeeffdce30e46c21de198da49ec81d6789bff75fb25b41632091f2e74840a6a497a1dd93d1d8fe37d7fc3da009f3b
7
- data.tar.gz: 0c79d3e330a252443148c7e6b92ae3ac255afe1140a59f801b6bc225d19ac547dfc725f6c906bdd5996b77020e6003687d5709cf23776cfb4f60290a3df5831e
6
+ metadata.gz: fa82429a9a668dafdc268f9bc85d46261b9b9742a31099c87cb37870ce8cc6af3b8c358cc5babd5ebfd8e39549caa16344aef181737fdb33a1616b1696423cf4
7
+ data.tar.gz: 82af4730e2b2b13dc9868398a65b76f46561663ba0e158b606e8502aa3a4c569b9b42b7e22f83399420cbae3ad19368908c8fd0d01c4e3c036db594ed20e64f4
data/.gitignore CHANGED
@@ -1,2 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
1
15
  *.gem
2
- lib/box_packer/test.rb
@@ -0,0 +1,4 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+ Documentation:
4
+ Enabled: false
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  BoxPacker
2
2
  =========
3
3
 
4
- A heuristic first-fit 3D bin packing algorithm with optional weight and bin limits.
4
+ A heuristic first-fit 3D bin packing algorithm with optional weight and bin limits.
5
5
 
6
6
  Installation
7
7
  ------------
@@ -24,44 +24,43 @@ Usage
24
24
  ``` ruby
25
25
  require 'box_packer'
26
26
 
27
- BoxPacker.container [3, 6, 7] do
28
- add_item [1,3,5]
29
- add_item [4,3,5]
30
- add_item [3,5,5]
31
- pack! # returns 2
32
-
33
- puts packed_successfully # true
34
- puts packings.count # 2
35
- puts packings[0].include? items[1] # false
36
- puts packings[0][1].position # (5,0,0)
37
-
38
- puts self # |Container| 7x6x3
39
- # | Packing| Remaining Volume:36
40
- # | Item| 5x5x3 (0,0,0) Volume:75
41
- # | Item| 1x5x3 (5,0,0) Volume:15
42
- # | Packing| Remaining Volume:66
43
- # | Item| 5x4x3 (0,0,0) Volume:60
44
-
27
+ BoxPacker.container [3, 6, 7] do
28
+ add_item [1,3,5]
29
+ add_item [4,3,5]
30
+ add_item [3,5,5]
31
+ pack! # returns 2
32
+
33
+ puts packed_successfully # true
34
+ puts packings.count # 2
35
+ puts packings[0].include? items[1] # false
36
+ puts packings[0][1].position # (5,0,0)
37
+
38
+ puts self # |Container| 7x6x3
39
+ # | Packing| Remaining Volume:36
40
+ # | Item| 5x5x3 (0,0,0) Volume:75
41
+ # | Item| 1x5x3 (5,0,0) Volume:15
42
+ # | Packing| Remaining Volume:66
43
+ # | Item| 5x4x3 (0,0,0) Volume:60
45
44
  end
46
45
  ```
47
46
 
48
47
  With optional labels, weights, quantity and packings limit:
49
48
 
50
49
  ``` ruby
51
- BoxPacker.container [15, 20, 13], label: 'Parcel', weight_limit: 50, packings_limit: 3 do
52
- add_item [2, 3, 5], label: 'Shoes', weight: 47, quantity: 2
53
- add_item [3, 3, 1], label: 'Watch', weight: 24
54
- add_item [1, 1, 4], label: 'Bag', weight: 7
55
- pack! # returns 2
56
-
57
- puts self # |Container| Parcel 20x15x13 Weight Limit:50 Packings Limit:3
58
- # | Packing| Remaining Volume:3870 Remaining Weight:3
59
- # | Item| Shoes 5x3x2 (0,0,0) Volume:30 Weight:47
60
- # | Packing| Remaining Volume:3870 Remaining Weight:3
61
- # | Item| Shoes 5x3x2 (0,0,0) Volume:30 Weight:47
62
- # | Packing| Remaining Volume:3887 Remaining Weight:19
63
- # | Item| Watch 3x3x1 (0,0,0) Volume:9 Weight:24
64
- # | Item| Bag 4x1x1 (3,0,0) Volume:4 Weight:7
50
+ BoxPacker.container [15, 20, 13], label: 'Parcel', weight_limit: 50, packings_limit: 3 do
51
+ add_item [2, 3, 5], label: 'Shoes', weight: 47, quantity: 2
52
+ add_item [3, 3, 1], label: 'Watch', weight: 24
53
+ add_item [1, 1, 4], label: 'Bag', weight: 7
54
+ pack! # returns 2
55
+
56
+ puts self # |Container| Parcel 20x15x13 Weight Limit:50 Packings Limit:3
57
+ # | Packing| Remaining Volume:3870 Remaining Weight:3
58
+ # | Item| Shoes 5x3x2 (0,0,0) Volume:30 Weight:47
59
+ # | Packing| Remaining Volume:3870 Remaining Weight:3
60
+ # | Item| Shoes 5x3x2 (0,0,0) Volume:30 Weight:47
61
+ # | Packing| Remaining Volume:3887 Remaining Weight:19
62
+ # | Item| Watch 3x3x1 (0,0,0) Volume:9 Weight:24
63
+ # | Item| Bag 4x1x1 (3,0,0) Volume:4 Weight:7
65
64
  end
66
65
  ```
67
66
 
@@ -69,27 +68,27 @@ Alternative builder API:
69
68
 
70
69
  ``` ruby
71
70
  BoxPacker.builder do |b|
72
- c1 = b.container [10,5,11]
73
- c2 = b.container [17,23,14]
71
+ c1 = b.container [10,5,11]
72
+ c2 = b.container [17,23,14]
74
73
 
75
- c1.items = [b.item([1,1,4]), b.item([4,6,7]), b.item([5,8,10])]
76
- c2.items = c1.items
74
+ c1.items = [b.item([1,1,4]), b.item([4,6,7]), b.item([5,8,10])]
75
+ c2.items = c1.items
77
76
 
78
- c1.pack! # 2
79
- c2.pack! # 1
77
+ c1.pack! # 2
78
+ c2.pack! # 1
80
79
 
81
- puts c1 # |Container| 11x10x5
82
- # | Packing| Remaining Volume:146
83
- # | Item| 10x8x5 (0,0,0) Volume:400
84
- # | Item| 4x1x1 (0,8,0) Volume:4
85
- # | Packing| Remaining Volume:382
86
- # | Item| 7x6x4 (10,0,0) Volume:168
80
+ puts c1 # |Container| 11x10x5
81
+ # | Packing| Remaining Volume:146
82
+ # | Item| 10x8x5 (0,0,0) Volume:400
83
+ # | Item| 4x1x1 (0,8,0) Volume:4
84
+ # | Packing| Remaining Volume:382
85
+ # | Item| 7x6x4 (10,0,0) Volume:168
87
86
 
88
- puts c2 # |Container| 23x17x14
89
- # | Packing| Remaining Volume:4902
90
- # | Item| 10x8x5 (0,0,0) Volume:400
91
- # | Item| 7x6x4 (10,0,0) Volume:168
92
- # | Item| 4x1x1 (17,0,0) Volume:4
87
+ puts c2 # |Container| 23x17x14
88
+ # | Packing| Remaining Volume:4902
89
+ # | Item| 10x8x5 (0,0,0) Volume:400
90
+ # | Item| 7x6x4 (10,0,0) Volume:168
91
+ # | Item| 4x1x1 (17,0,0) Volume:4
93
92
 
94
93
  end
95
94
  ```
@@ -98,14 +97,14 @@ Export SVG
98
97
  ----------
99
98
 
100
99
  ``` ruby
101
- BoxPacker.container [3, 4, 2] do
102
- add_item [1,3,2], label: 'Bag', colour: 'red'
103
- add_item [3,3,1], label: 'Hat', colour: 'blue'
104
- add_item [1,2,2], label: 'Shoes', colour: 'green'
105
- add_item [3,1,1], label: 'Slipper', colour: 'purple'
106
- add_item [2,1,1], label: 'Dragon', colour: 'orange'
107
- pack!
108
- draw!('examples/example', scale_longest_side_to: 500, margin: 15)
100
+ BoxPacker.container [3, 4, 2] do
101
+ add_item [1,3,2], label: 'Bag', colour: 'red'
102
+ add_item [3,3,1], label: 'Hat', colour: 'blue'
103
+ add_item [1,2,2], label: 'Shoes', colour: 'green'
104
+ add_item [3,1,1], label: 'Slipper', colour: 'purple'
105
+ add_item [2,1,1], label: 'Dragon', colour: 'orange'
106
+ pack!
107
+ draw!('examples/example', scale_longest_side_to: 500, margin: 15)
109
108
  end
110
109
  ```
111
110
 
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
@@ -7,21 +7,18 @@ Gem::Specification.new do |spec|
7
7
  spec.version = BoxPacker::VERSION
8
8
  spec.authors = ['Max White']
9
9
  spec.email = ['mushishi78@gmail.com']
10
- spec.description = 'A Heuristic First-Fit 3D Bin Packing Algorithm with Weight Limit'
11
- spec.summary = 'A Heuristic First-Fit 3D Bin Packing Algorithm with Weight Limit'
12
- spec.homepage = ''
10
+ spec.summary = 'Heuristic first-fit 3D bin-packing algorithm' \
11
+ 'with optional weight and bin limits.'
12
+ spec.homepage = 'https://github.com/mushishi78/box_packer'
13
13
  spec.license = 'MIT'
14
14
 
15
- spec.files = `git ls-files`.split($/)
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(/^(test|spec|features)/)
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_development_dependency 'bundler', '~> 1.3'
21
- spec.add_development_dependency 'rake'
22
- spec.add_development_dependency 'rspec'
23
-
24
- spec.add_dependency 'attr_extras', '~> 3.1.0'
25
- spec.add_dependency 'rasem', '~> 0.6.0'
20
+ spec.add_development_dependency 'rspec', '~> 3.1'
26
21
 
22
+ spec.add_dependency 'attire', '~> 0'
23
+ spec.add_dependency 'rasem', '~> 0.6'
27
24
  end
@@ -1,3 +1,13 @@
1
- require_relative 'box_packer/version'
2
- require_relative 'box_packer/builder'
3
- require_relative 'box_packer/container'
1
+ require 'attire'
2
+ require 'forwardable'
3
+
4
+ require 'box_packer/position'
5
+ require 'box_packer/dimensions'
6
+ require 'box_packer/box'
7
+ require 'box_packer/item'
8
+ require 'box_packer/packer'
9
+ require 'box_packer/packing'
10
+ require 'box_packer/svg_exporter'
11
+ require 'box_packer/container'
12
+ require 'box_packer/builder'
13
+ require 'box_packer/version'
@@ -1,40 +1,30 @@
1
- require 'attr_extras'
2
- require 'forwardable'
3
- require_relative 'position'
4
- require_relative 'dimensions'
5
-
6
1
  module BoxPacker
7
- class Box
8
- extend Forwardable
9
- attr_initialize :dimensions, [:position]
10
- def_delegators :dimensions, :volume, :each_rotation, :width, :height, :depth
11
- attr_accessor :dimensions, :position
12
-
13
- def position
14
- @position ||= Position[0, 0, 0]
15
- end
16
-
17
- def orient!
18
- @dimensions = Dimensions[*dimensions.to_a.sort!.reverse!]
19
- end
20
-
21
- def >=(other_box)
22
- dimensions >= other_box.dimensions
23
- end
24
-
25
- def sub_boxes(item)
26
- sub_boxes = sub_boxes_args(item).select{ |(d, p)| 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
-
39
- end
40
- end
2
+ class Box
3
+ extend Forwardable
4
+ attr_init :dimensions, position: Position[0, 0, 0]
5
+ def_delegators :dimensions, :volume, :each_rotation, :width, :height, :depth
6
+ attr_accessor :dimensions, :position
7
+
8
+ def orient!
9
+ @dimensions = Dimensions[*dimensions.to_a.sort!.reverse!]
10
+ end
11
+
12
+ def >=(other)
13
+ dimensions >= other.dimensions
14
+ end
15
+
16
+ def sub_boxes(item)
17
+ sub_boxes = sub_boxes_args(item).select { |(d, _)| d.volume > 0 }
18
+ sub_boxes.map! { |args| Box.new(*args) }
19
+ sub_boxes.sort_by!(&:volume).reverse!
20
+ end
21
+
22
+ private
23
+
24
+ def sub_boxes_args(item)
25
+ [[width + height + depth - item.width, position: position + item.width],
26
+ [item.width + height + depth - item.height, position: position + item.height],
27
+ [item.width + item.height + depth - item.depth, position: position + item.depth]]
28
+ end
29
+ end
30
+ end
@@ -1,22 +1,15 @@
1
- require_relative 'container'
2
- require_relative 'item'
3
-
4
1
  module BoxPacker
5
-
6
- def self.builder(&b)
7
- b.call(Builder.new) if block_given?
8
- end
9
-
10
- class Builder
11
-
12
- def container(*args, &b)
13
- Container.new(*args, &b)
14
- end
15
-
16
- def item(*args)
17
- Item.new(*args)
18
- end
19
-
20
- end
21
-
22
- end
2
+ def self.builder(&b)
3
+ b.call(Builder.new) if block_given?
4
+ end
5
+
6
+ class Builder
7
+ def container(*args, &b)
8
+ Container.new(*args, &b)
9
+ end
10
+
11
+ def item(*args)
12
+ Item.new(*args)
13
+ end
14
+ end
15
+ end
@@ -1,108 +1,95 @@
1
- require_relative 'box'
2
- require_relative 'packing'
3
- require_relative 'dimensions'
4
- require_relative 'item'
5
- require_relative 'packer'
6
- require_relative 'svg_exporter'
7
-
8
1
  module BoxPacker
9
-
10
- def self.container(*args, &b)
11
- Container.new(*args, &b)
12
- end
13
-
14
- class Container < Box
15
- attr_accessor :label, :weight_limit, :packings_limit
16
- attr_reader :items, :packing, :packings, :packed_successfully
17
-
18
- def initialize(dimensions, opts={}, &b)
19
- super(Dimensions[*dimensions])
20
- @label = opts[:label]
21
- @weight_limit = opts[:weight_limit]
22
- @packings_limit = opts[:packings_limit]
23
- @items = opts[:items] || []
24
- orient!
25
- self.instance_exec(&b) if block_given?
26
- end
27
-
28
- def add_item(dimensions, opts={})
29
- quantity = opts.delete(:quantity) || 1
30
- quantity.times do
31
- items << Item.new(dimensions, opts)
32
- end
33
- end
34
-
35
- def <<(item)
36
- items << item.dup
37
- end
38
-
39
- def items=(new_items)
40
- @items = new_items.map(&:dup)
41
- end
42
-
43
- def pack!
44
- prepare_to_pack!
45
- return unless packable?
46
- if @packed_successfully = Packer.pack(self)
47
- packings.count
48
- else
49
- @packings = []
50
- false
51
- end
52
- end
53
-
54
- def new_packing!
55
- @packing = Packing.new(volume, weight_limit)
56
- @packings << @packing
57
- end
58
-
59
- def to_s
60
- s = "\n|Container|"
61
- s << " #{label}" if label
62
- s << " #{dimensions}"
63
- s << " Weight Limit:#{weight_limit}" if weight_limit
64
- s << " Packings Limit:#{packings_limit}" if packings_limit
65
- s << "\n"
66
- if packed_successfully
67
- s << packings.map(&:to_s).join
68
- else
69
- s << "| | Did Not Pack!"
70
- end
71
- end
72
-
73
- def draw!(filename, opts={})
74
- exporter = SVGExporter.new(self, opts)
75
- exporter.draw
76
- exporter.save(filename)
77
- end
78
-
79
- private
80
-
81
- def packable?
82
- return false if items.empty?
83
- total_weight = 0
84
-
85
- items.each do |item|
86
- if weight_limit && item.weight
87
- return false if item.weight > weight_limit
88
- total_weight += item.weight
89
- end
90
-
91
- return false unless self >= item
92
- end
93
-
94
- if weight_limit && packings_limit
95
- return total_weight <= weight_limit * packings_limit
96
- end
97
-
98
- true
99
- end
100
-
101
- def prepare_to_pack!
102
- items.each(&:orient!)
103
- @packings = []
104
- @packed_successfully = false
105
- end
106
-
107
- end
108
- end
2
+ def self.container(*args, &b)
3
+ Container.new(*args, &b)
4
+ end
5
+
6
+ class Container < Box
7
+ attr_accessor :label, :weight_limit, :packings_limit
8
+ attr_reader :items, :packing, :packings, :packed_successfully
9
+
10
+ def initialize(dimensions, opts = {}, &b)
11
+ super(Dimensions[*dimensions])
12
+ @label = opts[:label]
13
+ @weight_limit = opts[:weight_limit]
14
+ @packings_limit = opts[:packings_limit]
15
+ @items = opts[:items] || []
16
+ orient!
17
+ instance_exec(&b) if b
18
+ end
19
+
20
+ def add_item(dimensions, opts = {})
21
+ quantity = opts.delete(:quantity) || 1
22
+ quantity.times do
23
+ items << Item.new(dimensions, opts)
24
+ end
25
+ end
26
+
27
+ def <<(item)
28
+ items << item.dup
29
+ end
30
+
31
+ def items=(new_items)
32
+ @items = new_items.map(&:dup)
33
+ end
34
+
35
+ def pack!
36
+ prepare_to_pack!
37
+ return unless packable?
38
+ if @packed_successfully = Packer.pack(self)
39
+ packings.count
40
+ else
41
+ @packings = []
42
+ false
43
+ end
44
+ end
45
+
46
+ def new_packing!
47
+ @packing = Packing.new(volume, weight_limit)
48
+ @packings << @packing
49
+ end
50
+
51
+ def to_s
52
+ s = "\n|Container|"
53
+ s << " #{label}" if label
54
+ s << " #{dimensions}"
55
+ s << " Weight Limit:#{weight_limit}" if weight_limit
56
+ s << " Packings Limit:#{packings_limit}" if packings_limit
57
+ s << "\n"
58
+ s << packed_successfully ? packings.map(&:to_s).join : '| | Did Not Pack!'
59
+ end
60
+
61
+ def draw!(filename, opts = {})
62
+ exporter = SVGExporter.new(self, opts)
63
+ exporter.draw
64
+ exporter.save(filename)
65
+ end
66
+
67
+ private
68
+
69
+ def packable?
70
+ return false if items.empty?
71
+ total_weight = 0
72
+
73
+ items.each do |item|
74
+ if weight_limit && item.weight
75
+ return false if item.weight > weight_limit
76
+ total_weight += item.weight
77
+ end
78
+
79
+ return false unless self >= item
80
+ end
81
+
82
+ if weight_limit && packings_limit
83
+ return total_weight <= weight_limit * packings_limit
84
+ end
85
+
86
+ true
87
+ end
88
+
89
+ def prepare_to_pack!
90
+ items.each(&:orient!)
91
+ @packings = []
92
+ @packed_successfully = false
93
+ end
94
+ end
95
+ end