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.
@@ -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