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.
@@ -1,45 +1,43 @@
1
1
  require 'matrix'
2
2
 
3
3
  module BoxPacker
4
- class Dimensions < Vector
5
-
6
- def +(d)
7
- Dimensions[*super]
8
- end
9
-
10
- def -(d)
11
- Dimensions[*super]
12
- end
13
-
14
- def width
15
- Dimensions[self[0], 0, 0]
16
- end
17
-
18
- def height
19
- Dimensions[0, self[1], 0]
20
- end
21
-
22
- def depth
23
- Dimensions[0, 0, self[2]]
24
- end
25
-
26
- def volume
27
- @volume ||= self[0] * self[1] * self[2]
28
- end
29
-
30
- def >=(other_dimensions)
31
- map2(other_dimensions){ |v1, v2| v1 >= v2 }.reduce(&:&)
32
- end
33
-
34
- def each_rotation
35
- to_a.permutation.each do |perm|
36
- yield Dimensions[*perm]
37
- end
38
- end
39
-
40
- def to_s
41
- "#{self[0]}x#{self[1]}x#{self[2]}"
42
- end
43
-
44
- end
45
- end
4
+ class Dimensions < Vector
5
+ def +(other)
6
+ Dimensions[*super]
7
+ end
8
+
9
+ def -(other)
10
+ Dimensions[*super]
11
+ end
12
+
13
+ def width
14
+ Dimensions[self[0], 0, 0]
15
+ end
16
+
17
+ def height
18
+ Dimensions[0, self[1], 0]
19
+ end
20
+
21
+ def depth
22
+ Dimensions[0, 0, self[2]]
23
+ end
24
+
25
+ def volume
26
+ @volume ||= self[0] * self[1] * self[2]
27
+ end
28
+
29
+ def >=(other)
30
+ map2(other) { |v1, v2| v1 >= v2 }.reduce(&:&)
31
+ end
32
+
33
+ def each_rotation
34
+ to_a.permutation.each do |perm|
35
+ yield Dimensions[*perm]
36
+ end
37
+ end
38
+
39
+ def to_s
40
+ "#{self[0]}x#{self[1]}x#{self[2]}"
41
+ end
42
+ end
43
+ end
@@ -1,35 +1,31 @@
1
- require_relative 'box'
2
- require_relative 'dimensions'
3
-
4
1
  module BoxPacker
5
- class Item < Box
6
- attr_accessor :label, :weight
7
- attr_reader :colour
2
+ class Item < Box
3
+ attr_accessor :label, :weight
4
+ attr_reader :colour
8
5
 
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
6
+ def initialize(dimensions, opts = {})
7
+ super(Dimensions[*dimensions])
8
+ @label = opts[:label].to_s
9
+ @weight = opts[:weight]
10
+ @colour = opts[:colour] || '%06x' % (rand * 0xffffff)
11
+ end
15
12
 
16
- def 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
13
+ def fit_into?(box)
14
+ each_rotation do |rotation|
15
+ if box.dimensions >= rotation
16
+ @dimensions = rotation
17
+ return true
18
+ end
19
+ end
20
+ false
21
+ end
25
22
 
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
-
34
- end
35
- end
23
+ def to_s
24
+ s = '| Item|'
25
+ s << " #{label}" if label
26
+ s << " #{dimensions} #{position} Volume:#{volume}"
27
+ s << " Weight:#{weight}" if weight
28
+ s << "\n"
29
+ end
30
+ end
31
+ end
@@ -1,58 +1,53 @@
1
- require 'attr_extras'
2
- require 'forwardable'
3
-
4
1
  module BoxPacker
5
- class Packer
6
- extend Forwardable
7
- method_object :pack, :container
8
- def_delegators :container, :new_packing!, :packings_limit, :packings, :packing
9
-
10
- def pack
11
- @items = container.items.sort_by!(&:volume).reverse!
12
-
13
- until too_many_packings? do
14
- new_packing!
15
- pack_box(@items, container)
16
- return true if @items.empty?
17
- end
18
- false
19
- end
20
-
21
- private
22
-
23
- def too_many_packings?
24
- packings.count >= packings_limit if packings_limit
25
- end
26
-
27
- def pack_box(possible_items, box)
28
- possible_items = possible_items.dup
29
- until possible_items.empty?
30
- item = possible_items.shift
31
-
32
- if item.fit_into?(box)
33
- pack_item!(item, possible_items, box)
34
- break if possible_items.empty?
35
-
36
- box.sub_boxes(item).each do |sub_box|
37
- purge!(possible_items)
38
- pack_box(possible_items, sub_box)
39
- end
40
- break
41
- end
42
- end
43
- end
44
-
45
- def pack_item!(item, possible_items, box)
46
- item.position = box.position
47
- possible_items.delete(item)
48
- packing << @items.delete(item)
49
- end
50
-
51
- def purge!(possible_items)
52
- possible_items.keep_if do |item|
53
- packing.fit?(item)
54
- end
55
- end
56
-
57
- end
58
- end
2
+ class Packer
3
+ extend Forwardable
4
+ attr_method :pack, :container
5
+ def_delegators :container, :new_packing!, :packings_limit, :packings, :packing
6
+
7
+ def pack
8
+ @items = container.items.sort_by!(&:volume).reverse!
9
+
10
+ until too_many_packings?
11
+ new_packing!
12
+ pack_box(@items, container)
13
+ return true if @items.empty?
14
+ end
15
+ false
16
+ end
17
+
18
+ private
19
+
20
+ def too_many_packings?
21
+ packings.count >= packings_limit if packings_limit
22
+ end
23
+
24
+ def pack_box(possible_items, box)
25
+ possible_items = possible_items.dup
26
+ until possible_items.empty?
27
+ item = possible_items.shift
28
+ next unless item.fit_into?(box)
29
+
30
+ pack_item!(item, possible_items, box)
31
+ break if possible_items.empty?
32
+
33
+ box.sub_boxes(item).each do |sub_box|
34
+ purge!(possible_items)
35
+ pack_box(possible_items, sub_box)
36
+ end
37
+ break
38
+ end
39
+ end
40
+
41
+ def pack_item!(item, possible_items, box)
42
+ item.position = box.position
43
+ possible_items.delete(item)
44
+ packing << @items.delete(item)
45
+ end
46
+
47
+ def purge!(possible_items)
48
+ possible_items.keep_if do |item|
49
+ packing.fit?(item)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,40 +1,39 @@
1
1
  require 'delegate'
2
2
 
3
3
  module BoxPacker
4
- class Packing < SimpleDelegator
5
- attr_reader :remaining_weight, :remaining_volume
4
+ class Packing < SimpleDelegator
5
+ attr_reader :remaining_weight, :remaining_volume
6
6
 
7
- def initialize(total_volume, total_weight)
8
- super([])
9
- @remaining_volume = total_volume
10
- @remaining_weight = total_weight
11
- end
7
+ def initialize(total_volume, total_weight)
8
+ super([])
9
+ @remaining_volume = total_volume
10
+ @remaining_weight = total_weight
11
+ end
12
12
 
13
- def <<(item)
14
- @remaining_volume -= item.volume
15
- @remaining_weight -= item.weight if weight?(item)
16
- super
17
- end
13
+ def <<(item)
14
+ @remaining_volume -= item.volume
15
+ @remaining_weight -= item.weight if weight?(item)
16
+ super
17
+ end
18
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
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
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
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
34
32
 
35
- def weight?(item)
36
- remaining_weight && item.weight
37
- end
33
+ private
38
34
 
39
- end
40
- end
35
+ def weight?(item)
36
+ remaining_weight && item.weight
37
+ end
38
+ end
39
+ end
@@ -1,11 +1,9 @@
1
1
  require 'matrix'
2
2
 
3
3
  module BoxPacker
4
- class Position < Vector
5
-
6
- def to_s
7
- "(#{self[0]},#{self[1]},#{self[2]})"
8
- end
9
-
10
- end
11
- end
4
+ class Position < Vector
5
+ def to_s
6
+ "(#{self[0]},#{self[1]},#{self[2]})"
7
+ end
8
+ end
9
+ end
@@ -1,122 +1,118 @@
1
- require 'attr_extras'
2
1
  require 'rasem'
3
2
 
4
3
  module BoxPacker
5
- class SVGExporter
6
- attr_private :container, :scale, :margin,
7
- :images, :image, :image_width, :image_height
8
-
9
- def initialize(container, opts={})
10
- @container = container
11
- @images = []
12
- @margin = opts[:margin] || 10
13
-
14
- dimensions = container.dimensions.to_a
15
- longest_side = dimensions.max
16
- sides_total = dimensions.reduce(&:+)
17
- scale_longest_side_to = opts[:scale_longest_side_to] || 400
18
-
19
- @scale = scale_longest_side_to / longest_side.to_f
20
- @image_width = (longest_side * scale * 2) + (margin * 3)
21
- @image_height = (sides_total * scale) + (margin * 4)
22
- end
23
-
24
- def save(filename)
25
- images.each_with_index do |image, i|
26
- image.close
27
-
28
- File.open("#{filename}#{i + 1}.svg" , "w") do |f|
29
- f << image.output
30
- end
31
- end
32
- end
33
-
34
- def draw
35
- container.packings.each do |packing|
36
- Face.reset(margin, scale, container.dimensions)
37
- new_image
38
- 6.times do
39
- face = Face.new(packing)
40
- image.rectangle(*face.outline, stroke: 'black', stroke_width: 1, fill: "white")
41
- face.rectangles_and_labels.each do |h|
42
- image.rectangle(*h[:rectangle])
43
- image.text(*h[:label])
44
- end
45
- end
46
-
47
- end
48
- end
49
-
50
- private
51
-
52
- def new_image
53
- @image = Rasem::SVGImage.new(image_width, image_height)
54
- images << image
55
- end
56
-
57
- class Face
58
- attr_reader :width, :height, :axes
59
- attr_private :packing, :i, :j, :k, :front, :items
60
- attr_query :front?
61
-
62
- def self.reset(margin, scale, container_dimensions)
63
- @@coords_mapping = [0,1,2]
64
- @@front = true
65
- @@margin = margin
66
- @@axes = [margin, margin]
67
- @@scale = scale
68
- @@container_dimensions = container_dimensions
69
- end
70
-
71
- def iterate_class_variables
72
- if front?
73
- @@axes[0] = width + @@margin * 2
74
- else
75
- @@coords_mapping.rotate!
76
- @@axes[0] = @@margin
77
- @@axes[1] += height + @@margin
78
- end
79
- @@front = !@@front
80
- end
81
-
82
- def initialize(packing)
83
- @i, @j, @k = @@coords_mapping.dup
84
- @front = @@front
85
- @axes = @@axes.dup
86
- @width = @@container_dimensions[i] * @@scale
87
- @height = @@container_dimensions[j] * @@scale
88
- iterate_class_variables
89
- @items = sorted_items(packing)
90
- end
91
-
92
- def outline
93
- @axes + [width, height]
94
- end
95
-
96
- def rectangles_and_labels
97
- items.map do |item|
98
- x = axes[0] + item.position[i] * @@scale
99
- y = axes[1] + item.position[j] * @@scale
100
- width = item.dimensions[i] * @@scale
101
- height = item.dimensions[j] * @@scale
102
- label_x = x + width / 2 - item.label.length
103
- label_y = y + height / 2
104
- {
105
- rectangle: [x, y, width, height, fill: item.colour],
106
- label: [label_x, label_y, item.label]
107
- }
108
- end
109
- end
110
-
111
- private
112
-
113
- def sorted_items(packing)
114
- items = packing.sort_by{ |i| i.position[k] }
115
- items.reverse! unless front?
116
- items
117
- end
118
-
119
- end
120
-
121
- end
122
- end
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
+ attr_query :front?
57
+
58
+ def self.reset(margin, scale, container_dimensions)
59
+ @@coords_mapping = [0, 1, 2]
60
+ @@front = true
61
+ @@margin = margin
62
+ @@axes = [margin, margin]
63
+ @@scale = scale
64
+ @@container_dimensions = container_dimensions
65
+ end
66
+
67
+ def iterate_class_variables
68
+ if front?
69
+ @@axes[0] = width + @@margin * 2
70
+ else
71
+ @@coords_mapping.rotate!
72
+ @@axes[0] = @@margin
73
+ @@axes[1] += height + @@margin
74
+ end
75
+ @@front = !@@front
76
+ end
77
+
78
+ def initialize(packing)
79
+ @i, @j, @k = @@coords_mapping.dup
80
+ @front = @@front
81
+ @axes = @@axes.dup
82
+ @width = @@container_dimensions[i] * @@scale
83
+ @height = @@container_dimensions[j] * @@scale
84
+ iterate_class_variables
85
+ @items = sorted_items(packing)
86
+ end
87
+
88
+ def outline
89
+ @axes + [width, height]
90
+ end
91
+
92
+ def rectangles_and_labels
93
+ items.map do |item|
94
+ x = axes[0] + item.position[i] * @@scale
95
+ y = axes[1] + item.position[j] * @@scale
96
+ width = item.dimensions[i] * @@scale
97
+ height = item.dimensions[j] * @@scale
98
+ label_x = x + width / 2 - item.label.length
99
+ label_y = y + height / 2
100
+ {
101
+ rectangle: [x, y, width, height, fill: item.colour],
102
+ label: [label_x, label_y, item.label]
103
+ }
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ attr_reader :packing, :i, :j, :k, :front, :items
110
+
111
+ def sorted_items(packing)
112
+ items = packing.sort_by { |i| i.position[k] }
113
+ items.reverse! unless front?
114
+ items
115
+ end
116
+ end
117
+ end
118
+ end