easy-box-packer-plus 0.0.5

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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +131 -0
  4. data/easy-box-packer-plus.rb +476 -0
  5. metadata +83 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fb6b977f33b387d3733c82b4faec7f1580c5f60e1e467d621751022cfe73d513
4
+ data.tar.gz: d4a624202c61440e27047d2334f94afbd8a272a1d5f560cc32339ed0abad71d9
5
+ SHA512:
6
+ metadata.gz: 8ae1deab86f2cec0b1749c87be6298506d4c4e9614abeb5baa5a519adda3dca59ad076a9444d8eb022e9e663a2663b295349449bdf92b04a738b7373432c4d94
7
+ data.tar.gz: 6b304ad8faea9e6fc00bbca9ae909b03e752f73ca751259ab4250301ad9392c237d895de1eacac977d23e0099cef3d96821e7b6bd086f0d284b969fc24db6a52
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Marcelo Alarcon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # EasyBoxPackerPlus
2
+
3
+ This is a clone of the [gem easy-box-packer](https://github.com/alChaCC/easy-box-packer) with added functionality to pass a hash with custom options for the items. I want to extend my gratitude to the original authors, [@mushishi78](https://github.com/mushishi78) of the [box_packer gem](https://github.com/mushishi78/box_packer/) and [@alChaCC](https://github.com/alChaCC) of the [easy-box-packer gem](https://github.com/alChaCC/easy-box-packer), for their efforts in the algorithm inspired by ["The Three-Dimensional Bin Packing Problem with Heterogeneous Bins"](https://www.researchgate.net/publication/273121476_A_genetic_algorithm_for_the_three-dimensional_bin_packing_problem_with_heterogeneous_bins) and ["A New Heuristic Algorithm for the 3D Bin Packing Problem."](https://www.researchgate.net/publication/226249396_A_New_Heuristic_Algorithm_for_the_3D_Bin_Packing_Problem).
4
+ This gem was created because the latter has not been updated in over six years.
5
+
6
+ ## Installation
7
+
8
+ Add to gemfile:
9
+
10
+ ``` ruby
11
+ gem 'easy-box-packer-plus', '~> 0.0.4'
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ### Check all items can be packed in a container
17
+
18
+ ``` ruby
19
+ require 'easy-box-packer'
20
+
21
+ cont = EasyBoxPacker.pack(
22
+ container: { dimensions: [15, 20, 13], weight_limit: 50 },
23
+ items: [
24
+ { dimensions: [2, 3, 5], weight: 47, options: {id: '2221', label: 'green shoes' } },
25
+ { dimensions: [2, 3, 5], weight: 47, options: {id: '2222', label: 'orange cap' } },
26
+ { dimensions: [3, 3, 1], weight: 24, options: {id: '2223', label: 'blue jeans' } },
27
+ { dimensions: [1, 1, 4], weight: 7, options: {id: '2224', label: 'black coat' } },
28
+ ]
29
+ )
30
+
31
+ cont[:packings].length # 3
32
+ cont[:packings][0][:weight] # 47
33
+ cont[:packings][0][:placements].length # 1
34
+ cont[:packings][0][:placements][0][:dimensions] # [2, 3, 5]
35
+ cont[:packings][0][:placements][0][:position] # [0, 0, 0]
36
+ cont[:packings][1][:weight] # 47
37
+ cont[:packings][1][:placements].length # 1
38
+ cont[:packings][1][:placements][0][:dimensions] # [2, 3, 5]
39
+ cont[:packings][1][:placements][0][:position] # [0, 0, 0]
40
+ cont[:packings][2][:weight] # 31
41
+ cont[:packings][2][:placements].length # 2
42
+ cont[:packings][2][:placements][0][:dimensions] # [1, 3, 3]
43
+ cont[:packings][2][:placements][0][:position] # [0, 0, 0]
44
+ cont[:packings][2][:placements][1][:dimensions] # [4, 1, 1]
45
+ cont[:packings][2][:placements][1][:position] # [3, 0, 0]
46
+ ```
47
+ Now you can access any key you have placed inside the options hash within the items hash:
48
+ ```
49
+ {
50
+ "packings": [{
51
+ "placements": [{
52
+ "dimensions": [2, 3, 5],
53
+ "position": [0, 0, 0],
54
+ "weight": 47.0,
55
+ "id": "2222",
56
+ "label": "orange cap"
57
+ }],
58
+ "weight": 47.0,
59
+ "spaces": [{
60
+ "dimensions": [18, 15, 13],
61
+ "position": [2, 0, 0]
62
+ }, {
63
+ "dimensions": [2, 12, 13],
64
+ "position": [0, 3, 0]
65
+ }, {
66
+ "dimensions": [2, 3, 8],
67
+ "position": [0, 0, 5]
68
+ }]
69
+ }, {
70
+ "placements": [{
71
+ "dimensions": [2, 3, 5],
72
+ "position": [0, 0, 0],
73
+ "weight": 47.0,
74
+ "id": "2221",
75
+ "label": "green shoes"
76
+ }],
77
+ "weight": 47.0,
78
+ "spaces": [{
79
+ "dimensions": [18, 15, 13],
80
+ "position": [2, 0, 0]
81
+ }, {
82
+ "dimensions": [2, 12, 13],
83
+ "position": [0, 3, 0]
84
+ }, {
85
+ "dimensions": [2, 3, 8],
86
+ "position": [0, 0, 5]
87
+ }]
88
+ }, {
89
+ "placements": [{
90
+ "dimensions": [1, 1, 4],
91
+ "position": [0, 0, 0],
92
+ "weight": 7.0,
93
+ "id": "2224",
94
+ "label": "black coat"
95
+ }, {
96
+ "dimensions": [1, 3, 3],
97
+ "position": [0, 1, 0],
98
+ "weight": 24.0,
99
+ "id": "2223",
100
+ "label": "blue jeans"
101
+ }],
102
+ "weight": 31.0,
103
+ "spaces": [{
104
+ "dimensions": [19, 15, 13],
105
+ "position": [1, 0, 0]
106
+ }, {
107
+ "dimensions": [1, 1, 9],
108
+ "position": [0, 0, 4]
109
+ }, {
110
+ "dimensions": [1, 11, 13],
111
+ "position": [0, 4, 0]
112
+ }, {
113
+ "dimensions": [1, 3, 10],
114
+ "position": [0, 1, 3]
115
+ }, {
116
+ "dimensions": [0, 3, 3],
117
+ "position": [1, 1, 0]
118
+ }]
119
+ }],
120
+ "errors": []
121
+ }
122
+ ```
123
+
124
+ ### Get a reasonable smallest container by given boxes
125
+
126
+ ```
127
+ container = EasyBoxPacker.find_smallest_container(
128
+ items: Array.new(1000) {{ dimensions: [1, 1, 1] }}
129
+ )
130
+ container # [10, 10, 10]
131
+ ```
@@ -0,0 +1,476 @@
1
+ module EasyBoxPackerPlus
2
+ class << self
3
+ def pack(container:, items:)
4
+ packings = []
5
+ errors = []
6
+ # pack from biggest to smallest
7
+ items.sort_by { |h| h[:dimensions].sort.reverse }.reverse.each do |item|
8
+ # If the item is just too big for the container lets give up on this
9
+ if item[:weight].to_f > container[:weight_limit].to_f
10
+ errors << "Item: #{item} is too heavy for container"
11
+ next
12
+ end
13
+
14
+ # Need a bool so we can break out nested loops once it's been packed
15
+ item_has_been_packed = false
16
+
17
+ packings.each do |packing|
18
+ # If this packings going to be too big with this
19
+ # item as well then skip on to the next packing
20
+ next if packing[:weight].to_f + item[:weight].to_f > container[:weight_limit].to_f
21
+
22
+ # remove volume size = 0 (not possible to pack)
23
+ packing[:spaces].reject! { |space| space[:dimensions].inject(:*) == 0 }
24
+ # try minimum space first
25
+ packing[:spaces].sort_by { |h| h[:dimensions].sort }.each do |space|
26
+ # Try placing the item in this space,
27
+ # if it doesn't fit skip on the next space
28
+ next unless placement = place(item, space)
29
+ # Add the item to the packing and
30
+ # break up the surrounding spaces
31
+ packing[:placements] += [placement]
32
+ packing[:weight] += item[:weight].to_f.round(2)
33
+ packing[:spaces] -= [space]
34
+ packing[:spaces] += break_up_space(space, placement)
35
+ item_has_been_packed = true
36
+ break
37
+ end
38
+ break if item_has_been_packed
39
+ end
40
+ next if item_has_been_packed
41
+ # Can't fit in any of the spaces for the current packings
42
+ # so lets try a new space the size of the container
43
+ space = {
44
+ dimensions: container[:dimensions].sort.reverse,
45
+ position: [0, 0, 0]
46
+ }
47
+ placement = place(item, space)
48
+
49
+ # If it can't be placed in this space, then it's just
50
+ # too big for the container and we should abandon hope
51
+ unless placement
52
+ errors << "Item: #{item} cannot be placed in container"
53
+ next
54
+ end
55
+ # Otherwise lets put the item in a new packing
56
+ # and break up the remaing free space around it
57
+ packings += [{
58
+ placements: [placement],
59
+ weight: item[:weight].to_f.round(2),
60
+ spaces: break_up_space(space, placement)
61
+ }]
62
+ end
63
+
64
+ packings[0][:weight] = packings[0][:weight].round(2)
65
+
66
+ if packings.size > 1 && check_container_is_bigger_than_greedy_box(container, items)
67
+ { packings: generate_packing_for_greedy_box(items), errors: [] }
68
+ else
69
+ { packings: packings, errors: errors }
70
+ end
71
+ end
72
+
73
+ def find_smallest_container(items:)
74
+ possible_containers = []
75
+ invalid_containers = []
76
+
77
+ min_vol = items.map { |h| h[:dimensions].inject(&:*) }.inject(&:+)
78
+ # order items from biggest to smallest
79
+ sorted_items = items.sort_by { |h| h[:dimensions].sort }.reverse
80
+
81
+ # base_container = sorted_items.first
82
+ based_container = sorted_items.first
83
+
84
+ if sorted_items.size == 1
85
+ return based_container[:dimensions]
86
+ end
87
+
88
+ find_possible_container(
89
+ possible_containers: possible_containers,
90
+ invalid_containers: invalid_containers,
91
+ container: based_container[:dimensions],
92
+ items: sorted_items.map {|i| i[:dimensions]},
93
+ item_index: 1,
94
+ min_vol: min_vol)
95
+
96
+ select_container = possible_containers.map { |a| a.sort }.sort_by { |a| [a.inject(&:*), a.inject(&:+)] }.each do |c|
97
+ packing = pack(
98
+ container: { dimensions: c },
99
+ items: items)
100
+ break c if packing[:packings].size == 1 && packing[:errors].size == 0
101
+ end
102
+ check_container_is_bigger_than_greedy_box({dimensions: select_container}, items) ? item_greedy_box(items) : select_container
103
+ end
104
+
105
+ private
106
+
107
+ def std(contents)
108
+ n = contents.size
109
+ contents.map!(&:to_f)
110
+ mean = contents.reduce(&:+)/n
111
+ sum_sqr = contents.map {|x| x * x}.reduce(&:+)
112
+ Math.sqrt(((sum_sqr - n * mean * mean)/(n-1)).abs)
113
+ end
114
+
115
+ def find_possible_container(possible_containers:,invalid_containers:,container:, items:, item_index:, min_vol:)
116
+ return unless items[item_index]
117
+ c_length, c_width, c_height = container.sort.reverse
118
+ b_length, b_width, b_height = items[item_index].sort.reverse
119
+ c_permutations = [
120
+ [c_width, c_height, c_length],
121
+ [c_length, c_width, c_height],
122
+ [c_length, c_height, c_width]
123
+ ]
124
+ b_permutations = [
125
+ [b_width, b_height, b_length],
126
+ [b_length, b_width, b_height],
127
+ [b_length, b_height, b_width]
128
+ ]
129
+
130
+ tmp_possible_containers = []
131
+ # (1) loops base_container 3 rotations
132
+ c_permutations.each do |c_perm|
133
+ # (2) try to puts items to 3 points, then it will create 3 different possible containers
134
+ b_permutations.each do |b_perm|
135
+ tmp_possible_containers << [ c_perm[0] + b_perm[0], [c_perm[1], b_perm[1]].max, [c_perm[2], b_perm[2]].max]
136
+ tmp_possible_containers << [ [c_perm[0], b_perm[0]].max, c_perm[1] + b_perm[1], [c_perm[2], b_perm[2]].max]
137
+ tmp_possible_containers << [ [c_perm[0], b_perm[0]].max, [c_perm[1], b_perm[1]].max, c_perm[2]+ b_perm[2]]
138
+ end
139
+ end
140
+ removed_tried_container = tmp_possible_containers.map { |a| a.sort }.uniq - possible_containers - invalid_containers
141
+
142
+ return unless removed_tried_container.any?
143
+ # (3) loop all container from smallest spaces to biggest space
144
+ removed_tried_container.sort_by { |a| [a.inject(&:*), a.inject(&:+)] }.each do |cont|
145
+ # (4) next unless l * w * h >= minimum_space
146
+ if cont.inject(&:*) >= min_vol
147
+ possible_containers << cont
148
+ # else
149
+ # puts "invalid: #{cont}"
150
+ # invalid_containers << cont
151
+ # find_possible_container(possible_containers: possible_containers, invalid_containers: invalid_containers, container: cont, items: items, item_index: item_index + 1, min_vol: min_vol)
152
+ end
153
+ end
154
+ # minimum_space = (removed_tried_container).sort_by { |a| [a.inject(&:*), a.inject(&:+)] }.first
155
+ minimum_std = removed_tried_container.sort_by { |a| [std(a), a.inject(&:*), a.inject(&:+)] }.first
156
+ [minimum_std].uniq.compact.each do |cont|
157
+ find_possible_container(possible_containers: possible_containers, invalid_containers: invalid_containers, container: cont, items: items, item_index: item_index + 1, min_vol: min_vol)
158
+ end
159
+ end
160
+
161
+ def place(item, space)
162
+ item_length, item_width, item_height = item[:dimensions].sort.reverse
163
+
164
+ permutations = [
165
+ [item_width, item_height, item_length],
166
+ [item_width, item_length, item_height],
167
+ [item_height, item_width, item_length],
168
+ [item_height, item_length, item_width],
169
+ [item_length, item_width, item_height],
170
+ [item_length, item_height, item_width]
171
+ ]
172
+
173
+ possible_rotations_and_margins = []
174
+
175
+ permutations.each do |perm|
176
+ # Skip if the item does not fit with this orientation
177
+ next unless perm[0] <= space[:dimensions][0] &&
178
+ perm[1] <= space[:dimensions][1] &&
179
+ perm[2] <= space[:dimensions][2]
180
+
181
+ possible_margin = [space[:dimensions][0] - perm[0], space[:dimensions][1] - perm[1], space[:dimensions][2] - perm[2]]
182
+ possible_rotations_and_margins << { margin: possible_margin, rotation: perm }
183
+ end
184
+
185
+ # select rotation with smallest margin
186
+ final = possible_rotations_and_margins.sort_by { |a| a[:margin].sort }.first
187
+ return unless final
188
+
189
+ result = {
190
+ dimensions: final[:rotation].map {|dim| (dim).round(2)},
191
+ position: space[:position].map {|dim| (dim).round(2)},
192
+ weight: item[:weight].to_f,
193
+ }
194
+
195
+ unless item[:options].nil?
196
+ item[:options].each {|k,v| result[k] = v }
197
+ end
198
+
199
+ result
200
+ end
201
+
202
+ def break_up_space(space, placement)
203
+ return_possible_spaces = [
204
+ # HEIGHT SPACE => WIDTH => LENGTH
205
+ [
206
+ {
207
+ dimensions: [
208
+ space[:dimensions][0],
209
+ space[:dimensions][1],
210
+ space[:dimensions][2] - placement[:dimensions][2]
211
+ ],
212
+ position: [
213
+ space[:position][0],
214
+ space[:position][1],
215
+ space[:position][2] + placement[:dimensions][2]
216
+ ]
217
+ },
218
+ {
219
+ dimensions: [
220
+ space[:dimensions][0],
221
+ space[:dimensions][1] - placement[:dimensions][1],
222
+ placement[:dimensions][2]
223
+ ],
224
+ position: [
225
+ space[:position][0],
226
+ space[:position][1] + placement[:dimensions][1],
227
+ space[:position][2]
228
+ ]
229
+ },
230
+ {
231
+ dimensions: [
232
+ space[:dimensions][0] - placement[:dimensions][0],
233
+ placement[:dimensions][1],
234
+ placement[:dimensions][2]
235
+ ],
236
+ position: [
237
+ space[:position][0] + placement[:dimensions][0],
238
+ space[:position][1],
239
+ space[:position][2]
240
+ ]
241
+ }
242
+ ],
243
+ # HEIGHT SPACE => LENGTH => WIDTH
244
+ [
245
+ {
246
+ dimensions: [
247
+ space[:dimensions][0],
248
+ space[:dimensions][1],
249
+ space[:dimensions][2] - placement[:dimensions][2]
250
+ ],
251
+ position: [
252
+ space[:position][0],
253
+ space[:position][1],
254
+ space[:position][2] + placement[:dimensions][2]
255
+ ]
256
+ },
257
+ {
258
+ dimensions: [
259
+ space[:dimensions][0] - placement[:dimensions][0],
260
+ space[:dimensions][1],
261
+ placement[:dimensions][2]
262
+ ],
263
+ position: [
264
+ space[:position][0] + placement[:dimensions][0],
265
+ space[:position][1],
266
+ space[:position][2]
267
+ ]
268
+ },
269
+ {
270
+ dimensions: [
271
+ placement[:dimensions][0],
272
+ space[:dimensions][1] - placement[:dimensions][1],
273
+ placement[:dimensions][2]
274
+ ],
275
+ position: [
276
+ space[:position][0],
277
+ space[:position][1] + placement[:dimensions][1],
278
+ space[:position][2]
279
+ ]
280
+ }
281
+ ],
282
+ # LENGTH SPACE => HEIGHT => WIDTH
283
+ [
284
+ {
285
+ dimensions: [
286
+ space[:dimensions][0] - placement[:dimensions][0],
287
+ space[:dimensions][1],
288
+ space[:dimensions][2]
289
+ ],
290
+ position: [
291
+ space[:position][0] + placement[:dimensions][0],
292
+ space[:position][1],
293
+ space[:position][2]
294
+ ]
295
+ },
296
+ {
297
+ dimensions: [
298
+ placement[:dimensions][0],
299
+ space[:dimensions][1],
300
+ space[:dimensions][2] - placement[:dimensions][2]
301
+ ],
302
+ position: [
303
+ space[:position][0],
304
+ space[:position][1],
305
+ space[:position][2] + placement[:dimensions][2]
306
+ ]
307
+ },
308
+ {
309
+ dimensions: [
310
+ placement[:dimensions][0],
311
+ space[:dimensions][1] - placement[:dimensions][1],
312
+ placement[:dimensions][2]
313
+ ],
314
+ position: [
315
+ space[:position][0],
316
+ space[:position][1] + placement[:dimensions][1],
317
+ space[:position][2]
318
+ ]
319
+ }
320
+ ],
321
+ # LENGTH SPACE => WIDTH => HEIGHT
322
+ [
323
+ {
324
+ dimensions: [
325
+ space[:dimensions][0] - placement[:dimensions][0],
326
+ space[:dimensions][1],
327
+ space[:dimensions][2]
328
+ ],
329
+ position: [
330
+ space[:position][0] + placement[:dimensions][0],
331
+ space[:position][1],
332
+ space[:position][2]
333
+ ]
334
+ },
335
+ {
336
+ dimensions: [
337
+ placement[:dimensions][0],
338
+ space[:dimensions][1] - placement[:dimensions][1],
339
+ space[:dimensions][2]
340
+ ],
341
+ position: [
342
+ space[:position][0],
343
+ space[:position][1] + placement[:dimensions][1],
344
+ space[:position][2]
345
+ ]
346
+ },
347
+ {
348
+ dimensions: [
349
+ placement[:dimensions][0],
350
+ placement[:dimensions][1],
351
+ space[:dimensions][2] - placement[:dimensions][2]
352
+ ],
353
+ position: [
354
+ space[:position][0],
355
+ space[:position][1],
356
+ space[:position][2] + placement[:dimensions][2]
357
+ ]
358
+ }
359
+ ],
360
+ # WIDTH SPACE => LENGTH => HEIGHT
361
+ [
362
+ {
363
+ dimensions: [
364
+ space[:dimensions][0],
365
+ space[:dimensions][1] - placement[:dimensions][1],
366
+ space[:dimensions][2]
367
+ ],
368
+ position: [
369
+ space[:position][0],
370
+ space[:position][1] + placement[:dimensions][1],
371
+ space[:position][2]
372
+ ]
373
+ },
374
+ {
375
+ dimensions: [
376
+ space[:dimensions][0] - placement[:dimensions][0],
377
+ placement[:dimensions][1],
378
+ space[:dimensions][2]
379
+ ],
380
+ position: [
381
+ space[:position][0] + placement[:dimensions][0],
382
+ space[:position][1],
383
+ space[:position][2]
384
+ ]
385
+ },
386
+ {
387
+ dimensions: [
388
+ placement[:dimensions][0],
389
+ placement[:dimensions][1],
390
+ space[:dimensions][2] - placement[:dimensions][2]
391
+ ],
392
+ position: [
393
+ space[:position][0],
394
+ space[:position][1],
395
+ space[:position][2] + placement[:dimensions][2]
396
+ ]
397
+ }
398
+ ],
399
+ # WIDTH SPACE => HEIGHT => LENGTH
400
+ [
401
+ {
402
+ dimensions: [
403
+ space[:dimensions][0],
404
+ space[:dimensions][1] - placement[:dimensions][1],
405
+ space[:dimensions][2]
406
+ ],
407
+ position: [
408
+ space[:position][0],
409
+ space[:position][1] + placement[:dimensions][1],
410
+ space[:position][2]
411
+ ]
412
+ },
413
+ {
414
+ dimensions: [
415
+ space[:dimensions][0],
416
+ placement[:dimensions][1],
417
+ space[:dimensions][2] - placement[:dimensions][2]
418
+ ],
419
+ position: [
420
+ space[:position][0],
421
+ space[:position][1],
422
+ space[:position][2] + placement[:dimensions][2]
423
+ ]
424
+ },
425
+ {
426
+ dimensions: [
427
+ space[:dimensions][0] - placement[:dimensions][0],
428
+ placement[:dimensions][1],
429
+ placement[:dimensions][2]
430
+ ],
431
+ position: [
432
+ space[:position][0] + placement[:dimensions][0],
433
+ space[:position][1],
434
+ space[:position][2]
435
+ ]
436
+ }
437
+ ]
438
+ ]
439
+ # PICK biggest
440
+ dim_array = return_possible_spaces.sort_by { |a| a.map {|aa| aa[:dimensions].sort}}.last
441
+
442
+ result = dim_array&.map do |elem|
443
+ elem.transform_values do |value|
444
+ value.map { |v| v.round(2) }
445
+ end
446
+ end
447
+
448
+ result
449
+ end
450
+
451
+ def item_greedy_box(items)
452
+ array_of_lwh = items.map { |i| i[:dimensions].sort.reverse }
453
+ items_max_length = array_of_lwh.max { |x, y| x[0] <=> y[0] }[0]
454
+ items_max_width = array_of_lwh.max { |x, y| x[1] <=> y[1] }[1]
455
+ items_total_height = array_of_lwh.inject(0) { |sum, x| sum+=x[2] }.round(1)
456
+ [items_max_length, items_max_width, items_total_height]
457
+ end
458
+
459
+ def check_container_is_bigger_than_greedy_box(container, items)
460
+ c = container[:dimensions].sort.reverse
461
+ greedy_box = item_greedy_box(items)
462
+ c[0] >= greedy_box[0] && c[1] >= greedy_box[1] && c[2] >= greedy_box[2] && container[:weight_limit].to_f >= items.inject(0) { |sum, i| sum += i[:weight].to_f }
463
+ end
464
+
465
+ def generate_packing_for_greedy_box(items)
466
+ return_h = {placements: [], weight: 0, spaces: []}
467
+ height = 0
468
+ items.each do |i|
469
+ return_h[:placements] << { dimensions: i[:dimensions], :position=>[0, 0, height], weight: i[:weight].to_f }
470
+ return_h[:weight] += i[:weight].to_f
471
+ height += i[:dimensions].sort.first.to_f
472
+ end
473
+ [return_h]
474
+ end
475
+ end
476
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy-box-packer-plus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Marcelo Alarcon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.1.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: pry
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description:
48
+ email: marceloalarconbarrenechea@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - LICENSE
54
+ - README.md
55
+ - easy-box-packer-plus.rb
56
+ homepage: https://github.com/chelobotix/easy-box-packer-plus
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - "."
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.1.6
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: 3D bin-packing with weight limit using first-fit decreasing algorithm. The
79
+ functionality has been extended to include an internal options hash, allowing any
80
+ desired values to be passed to the items, such as IDs and labels for each item.
81
+ This ensures that when the gem returns the result, these keys are included in the
82
+ output.
83
+ test_files: []