boxify 0.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cbe525b48eb75a67ace2f93c09a24cb5b3e35065
4
+ data.tar.gz: 9392c2eb883e591352cecce4abe5a44ab217f366
5
+ SHA512:
6
+ metadata.gz: 032807b6118f66d235f55adbed4b26bdb47c04ef1d982a8af34e8b931c9fef961e9df1de9ddefeef68e3f899cdcdd96273c7617dca0a1218b6ab01b283efad72
7
+ data.tar.gz: 6a3a8c7f69e04b7d081ee76baf0ecf262d73553cce602b7ce4526330f5c9ec9827dffbe71dae399242092b4a0ab79db304013a1df5adb9d7bb2806934a02d997
@@ -0,0 +1,8 @@
1
+ require 'forwardable'
2
+ require_relative 'boxify/dimensionable'
3
+ require_relative 'boxify/container'
4
+ require_relative 'boxify/box'
5
+ require_relative 'boxify/space'
6
+ require_relative 'boxify/eligible_box'
7
+ require_relative 'boxify/placed_box'
8
+ require_relative 'boxify/pack'
@@ -0,0 +1,90 @@
1
+ module Boxify
2
+ class Box
3
+ include Dimensionable
4
+
5
+ attr_accessor :total_count
6
+
7
+ def initialize(width:, depth:, height:, total_count: 1)
8
+ @width = width
9
+ @depth = depth
10
+ @height = height
11
+ @total_count = total_count
12
+ end
13
+
14
+ def rotated
15
+ @rotated ||= Box.new(width: height, depth: depth, height: width)
16
+ end
17
+
18
+ def rotate
19
+ temp_width = width
20
+ @width = height
21
+ @height = temp_width
22
+ end
23
+ end
24
+
25
+ class BoxCollection
26
+
27
+ attr_reader :boxes
28
+
29
+ def initialize(boxes: boxes)
30
+ @boxes = boxes
31
+ end
32
+
33
+ def unplaced
34
+ boxes.select{ |b| b.total_count > 0 }
35
+ end
36
+
37
+ def delete(box)
38
+ return if box.total_count == 0
39
+ box.total_count -= 1
40
+ end
41
+
42
+ def total_count
43
+ boxes.map(&:total_count).inject(:+)
44
+ end
45
+
46
+ def flattened_dimensions
47
+ @flattened_dimensions ||= boxes.map { |b| [b.width, b.height, b.depth]}.flatten.uniq.sort
48
+ end
49
+
50
+ def longest_edge
51
+ @longest_edge ||= flattened_dimensions.max
52
+ end
53
+
54
+ def second_longest_edge
55
+ @second_longest_edge ||= flattened_dimensions[-2]
56
+ end
57
+
58
+ def box_with_widest_surface_area
59
+ boxes_sorted_by_surface_area.last
60
+ end
61
+
62
+ def box_with_minimum_height
63
+ boxes_sorted_by_height.first
64
+ end
65
+
66
+ def more_than_one_box_with_widest_surface_area?
67
+ number_of_boxes = unplaced.select {|b| b.surface_area == box_with_widest_surface_area.surface_area }.count
68
+ number_of_boxes > 1
69
+ end
70
+
71
+ def find_eligible_boxes(space)
72
+ unplaced.select{ |b| (b.width <= space.width) && (b.height <= space.height) }
73
+ end
74
+
75
+ # Find biggest (widest surface) box with minimum height
76
+ def find_biggest_box_with_minimum_height
77
+ more_than_one_box_with_widest_surface_area? ? box_with_minimum_height : box_with_widest_surface_area
78
+ end
79
+
80
+ private
81
+
82
+ def boxes_sorted_by_surface_area
83
+ unplaced.sort { |x,y| x.surface_area <=> y.surface_area }
84
+ end
85
+
86
+ def boxes_sorted_by_height
87
+ unplaced.sort { |x,y| x.height <=> y.height }
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,21 @@
1
+ module Boxify
2
+ class Container
3
+ include Dimensionable
4
+
5
+ attr_accessor :placed_boxes
6
+
7
+ def initialize(width:, depth:, height:)
8
+ @width = width
9
+ @depth = depth
10
+ @height = height
11
+ end
12
+
13
+ def volume_of_placed_boxes
14
+ placed_boxes.volume
15
+ end
16
+
17
+ def wasted_space_percentage
18
+ (volume - volume_of_placed_boxes).to_f / volume * 100
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ module Boxify
2
+ module Dimensionable
3
+ attr_accessor :width, :depth, :height
4
+
5
+ def area
6
+ width * depth
7
+ end
8
+
9
+ def volume
10
+ width * height * depth
11
+ end
12
+
13
+ def surface_area
14
+ (2 * height * width) + (2 * height * depth) + (2 * width * depth)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ module Boxify
2
+ class EligibleBox
3
+
4
+ attr_reader :boxes, :space
5
+
6
+ def initialize(boxes:, space:)
7
+ @boxes = boxes
8
+ @space = space
9
+ end
10
+
11
+ def self.find_all(boxes:, space:)
12
+ new(boxes: boxes, space: space).eligible_boxes
13
+ end
14
+
15
+ def self.find_best_fit(boxes:, space:)
16
+ new(boxes: boxes, space: space).eligible_box_with_largest_volume
17
+ end
18
+
19
+ def eligible_boxes
20
+ @eligible_boxes ||= find_eligible_boxes
21
+ end
22
+
23
+ def eligible_box_with_largest_volume
24
+ @eligible_box_with_largest_volume ||= find_box_with_largest_volume(eligible_boxes)
25
+ end
26
+
27
+ private
28
+
29
+ def find_eligible_boxes
30
+ da_boxes = boxes.select do |box|
31
+ true if (box.width <= space.width) && (box.height <= space.height)
32
+
33
+ rotated_copy = box.rotated
34
+ if (rotated_copy.width <= space.width) && (rotated_copy.height <= space.height)
35
+ box.rotate
36
+ true
37
+ end
38
+ end
39
+ da_boxes
40
+ end
41
+
42
+ def find_box_with_largest_volume(boxes)
43
+ if boxes.size > 1
44
+ # Return box with largest volume
45
+ boxes.sort{ |b| b.volume }.first
46
+ else
47
+ boxes.first
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,99 @@
1
+ module Boxify
2
+ class Pack
3
+ extend Forwardable
4
+
5
+ STARTING_LEVEL = 0
6
+
7
+ attr_reader :boxes, :placed_boxes, :level, :container, :volume_of_placed_boxes
8
+
9
+ def_delegators :@container, :volume_of_placed_boxes, :wasted_space_percentage
10
+ def_delegator :@container, :height, :container_height
11
+ def_delegator :@container, :depth, :container_depth
12
+ def_delegator :@container, :width, :container_width
13
+ def_delegator :@container, :volume, :container_volume
14
+
15
+ def initialize(boxes:)
16
+ @level = STARTING_LEVEL
17
+ @boxes = boxes
18
+ @placed_boxes = PlacedBoxCollection.new
19
+ @container = Container.new(width: boxes.longest_edge, depth: boxes.second_longest_edge, height: 0)
20
+ end
21
+
22
+ def pack
23
+ pack_level
24
+ container.placed_boxes = placed_boxes
25
+ true
26
+ end
27
+
28
+ def pack_level
29
+
30
+ # Increment level of box
31
+ increment_level
32
+
33
+ # Get biggest box as object
34
+ box = boxes.find_biggest_box_with_minimum_height
35
+
36
+ # Set container height (ck = ck + ci)
37
+ increment_height(box.height)
38
+
39
+ # Remove box from array (ki = ki - 1)
40
+ pack_box(box)
41
+
42
+ # Terminate if all boxes have been packed
43
+ return true if boxes.total_count == 0
44
+
45
+ # No space left (not even when rotated / length and width swapped)
46
+ if container.area - box.area <= 0
47
+ pack_level
48
+ else # Space left, check if a package fits in
49
+ space = Space.new(width: container.width, depth: container.depth, height: container.height)
50
+ spaces = SpaceCollection.find_spaces_within_space(space: space, box: box)
51
+
52
+ # Fill each space with boxes
53
+ spaces.each do |space|
54
+ fill_space(space)
55
+ end
56
+
57
+ pack_level if boxes.total_count > 0
58
+ end
59
+ end
60
+
61
+
62
+ # Fills space with boxes recursively
63
+ def fill_space(space)
64
+ # Find box that fits into this space
65
+ eligible_box = find_eligible_box(space)
66
+
67
+ if eligible_box
68
+ pack_box(eligible_box)
69
+
70
+ spaces = SpaceCollection.find_spaces_within_space(space: space, box: eligible_box)
71
+
72
+ # Fill each space with boxes
73
+ spaces.each do |space|
74
+ fill_space(space)
75
+ end
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Set container height (ck = ck + ci)
82
+ def increment_height(height)
83
+ @container.height += height
84
+ end
85
+
86
+ def increment_level
87
+ @level += 1
88
+ end
89
+
90
+ def pack_box(box)
91
+ placed_boxes.add(box: box, level: level)
92
+ boxes.delete(box)
93
+ end
94
+
95
+ def find_eligible_box(space)
96
+ EligibleBox.find_best_fit(boxes: boxes.unplaced, space: space)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,31 @@
1
+ module Boxify
2
+ class PlacedBox
3
+ extend Forwardable
4
+
5
+ attr_reader :box, :level
6
+
7
+ def_delegators :@box, :volume
8
+
9
+ def initialize(box:, level:)
10
+ @box = box
11
+ @level = level
12
+ end
13
+ end
14
+
15
+ class PlacedBoxCollection
16
+
17
+ attr_accessor :placed_boxes
18
+
19
+ def initialize(placed_boxes: [])
20
+ @placed_boxes = placed_boxes
21
+ end
22
+
23
+ def add(box:, level:)
24
+ @placed_boxes.push(PlacedBox.new(box: box, level: level))
25
+ end
26
+
27
+ def volume
28
+ placed_boxes.map(&:volume).inject(:+)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ module Boxify
2
+ class Space
3
+ include Dimensionable
4
+
5
+ def initialize(width:, depth:, height:)
6
+ @width = width
7
+ @depth = depth
8
+ @height = height
9
+ end
10
+ end
11
+
12
+ class SpaceCollection
13
+ extend Forwardable
14
+
15
+ attr_reader :spaces
16
+
17
+ def_delegators :@spaces, :each
18
+
19
+ def initialize(spaces: spaces)
20
+ @spaces = spaces
21
+ end
22
+
23
+ def self.find_spaces_within_space(space:, box:)
24
+ spaces = []
25
+
26
+ if space.depth - box.depth > 0
27
+ spaces.push(Space.new(depth: space.depth - box.depth,
28
+ width: space.width,
29
+ height: box.height))
30
+ end
31
+
32
+ if space.width - box.width > 0
33
+ spaces.push(Space.new(depth: box.depth,
34
+ width: space.width - box.width,
35
+ height: box.height))
36
+ end
37
+
38
+ new(spaces: spaces)
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: boxify
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Don Pottinger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Ruby implementation of a Largest Area Fit First (LAFF) for 3D Rectangular
14
+ Box Packing algorithm
15
+ email: don.pottinger@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/boxify.rb
21
+ - lib/boxify/box.rb
22
+ - lib/boxify/container.rb
23
+ - lib/boxify/dimensionable.rb
24
+ - lib/boxify/eligible_box.rb
25
+ - lib/boxify/pack.rb
26
+ - lib/boxify/placed_box.rb
27
+ - lib/boxify/space.rb
28
+ homepage: https://github.com/strukturedkaos/boxify
29
+ licenses:
30
+ - MIT
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.2.2
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: Ruby implementation of a Largest Area Fit First (LAFF) for 3D Rectangular
52
+ Box Packing algorithm
53
+ test_files: []