box_packer 1.1.2 → 1.2.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: 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