File Coverage

blib/lib/Box/Calc.pm
Criterion Covered Total %
statement 194 219 88.5
branch 23 36 63.8
condition 22 55 40.0
subroutine 26 29 89.6
pod 19 19 100.0
total 284 358 79.3


line stmt bran cond sub pod time code
1             package Box::Calc;
2             $Box::Calc::VERSION = '1.0201';
3 7     7   85268 use strict;
  7         22  
  7         204  
4 7     7   2993 use Moose;
  7         2752997  
  7         61  
5 7     7   56321 use Box::Calc::BoxType;
  7         100898  
  7         333  
6 7     7   3357 use Box::Calc::Item;
  7         2405  
  7         264  
7 7     7   3228 use Box::Calc::Box;
  7         2709  
  7         296  
8 7     7   4415 use List::MoreUtils qw(natatime);
  7         84968  
  7         49  
9 7     7   7228 use List::Util qw(max);
  7         19  
  7         521  
10 7     7   48 use Ouch;
  7         18  
  7         66  
11 7     7   603 use Log::Any qw($log);
  7         18  
  7         73  
12              
13             =head1 NAME
14              
15             Box::Calc - Packing Algorithm
16              
17             =head1 VERSION
18              
19             version 1.0201
20              
21             =head1 SYNOPSIS
22              
23             use Box::Calc;
24            
25             my $box_calc = Box::Calc->new;
26            
27             # define the possible box types
28             $box_calc->add_box_type( x => 12, y => 12, z => 18, weight => 16, name => 'big box' );
29             $box_calc->add_box_type( x => 4, y => 6, z => 8, weight => 6, name => 'small box' );
30              
31             # define the items you want to put into boxes
32             $box_calc->add_item( 3, { x => 6, y => 3, z => 3, weight => 12, name => 'soda' });
33             $box_calc->add_item( 1, { x => 3.3, y => 3, z => 4, weight => 4.5, name => 'apple' });
34             $box_calc->add_item( 2, { x => 8, y => 2.5, z => 2.5, weight => 14, name => 'water bottle' });
35              
36             # figure out what you need to pack this stuff
37             $box_calc->pack_items;
38            
39             # how many boxes do you need
40             my $box_count = $box_calc->count_boxes; # 2
41            
42             # interrogate the boxes
43             my $box = $box_calc->get_box(-1); # the last box
44             my $weight = $box->calculate_weight;
45            
46             # get a packing list
47             my $packing_list = $box_calc->packing_list;
48            
49             =head1 DESCRIPTION
50              
51             Box::Calc helps you determine what can fit into a box for shipping or storage purposes. It will try to use the smallest box possible of the box types. If every item won't fit into your largest box, then it will span the boxes letting you know how many boxes you'll need.
52              
53             Once it's done packing the boxes, you can get a packing list for each box, as well as the weight of each box.
54              
55             =head2 How The Algorithm Works
56              
57             Box::Calc is intended to pack boxes in the simplest way possible. Here's what it does:
58              
59             =over
60              
61             =item 1
62              
63             Sort all the items by volume.
64              
65             =item 2
66              
67             Eliminate all boxes that won't fit the largest items.
68              
69             =item 3
70              
71             Choose the smallest box still available.
72              
73             =item 4
74              
75             Place the items in a row starting with the largest items.
76              
77             =item 5
78              
79             When the row runs out of space, add another.
80              
81             =item 6
82              
83             When you run out of space to add rows, add a layer.
84              
85             =item 7
86              
87             When you run out of layers either start over with a bigger box, or if there are no bigger boxes span to a second box.
88              
89             =item 8
90              
91             Repeat from step 3 until all items are packed into boxes.
92              
93             =back
94              
95             =head2 Motivation
96              
97             At The Game Crafter (L<http://www.thegamecrafter.com>) we ship a lot of games and game pieces. We tried using a more complicated system for figuring out which size box to use, or how many boxes would be needed in a spanning situation. The problem was that those algorithms made the boxes pack so tightly that our staff spent a lot more time putting the boxes together. This algorithm is relatively dumb, but dumb in a good way. The boxes are easy and fast to pack. By releasing this, we hope it can help those who are either using too complicated a system, or no system at all for figuring out how many boxes they need for shipping/storing materials.
98              
99             =head2 Tips
100              
101             When adding items, be sure to use the outer most dimensions of oddly shaped items, otherwise they may not fit the box.
102              
103             When adding box types, be sure to use the inside dimensions of the box. If you plan to line the box with padding, then subtract the padding from the dimensions, and also add the padding to the weight of the box.
104              
105             What units you use (inches, centimeters, ounces, pounds, grams, kilograms, etc) don't matter as long as you use them consistently.
106              
107             =head1 METHODS
108              
109             =head2 new()
110              
111             Constructor.
112              
113             =head2 box_types()
114              
115             Returns an array reference of the L<Box::Calc::BoxType>s registered.
116              
117             =head2 count_box_types()
118              
119             Returns the number of L<Box::Calc::BoxType>s registered.
120              
121             =head2 get_box_type(index)
122              
123             Returns a specific L<Box::Calc::BoxType> from the list of C<box_types>
124              
125             =over
126              
127             =item index
128              
129             An array index. For example this would return the last box type added:
130              
131             $box_calc->get_box_type(-1)
132              
133             =back
134              
135             =cut
136              
137             has box_types => (
138             is => 'rw',
139             isa => 'ArrayRef[Box::Calc::BoxType]',
140             default => sub { [] },
141             traits => ['Array'],
142             handles => {
143             push_box_types => 'push',
144             count_box_types => 'count',
145             get_box_type => 'get',
146             }
147             );
148              
149             =head2 add_box_type(params)
150              
151             Adds a new L<Box::Calc::BoxType> to the list of C<box_types>. Returns the newly created L<Box::Calc::BoxType> instance.
152              
153             =over
154              
155             =item params
156              
157             The list of constructor parameters for L<Box::Calc::BoxType>.
158              
159             B<NOTE:> You can optionally include an argument of "categories" and a box type will be created for each category so you don't have to do it manually.
160              
161             =back
162              
163             =cut
164              
165             sub add_box_type {
166 28     28 1 2995 my $self = shift;
167 28         54 my $args;
168 28 100       78 if (ref $_[0] eq 'HASH') {
169 10         20 $args = shift;
170             }
171             else {
172 18         85 $args = { @_ };
173             }
174 28         57 my $categories = delete $args->{categories};
175 28 100       64 if (defined $categories) {
176 3         6 foreach my $category (@{$categories}) {
  3         6  
177 4         8 my %copy = %{$args};
  4         18  
178 4         9 $copy{category} = $category;
179 4         135 $self->push_box_types(Box::Calc::BoxType->new(%copy));
180             }
181             }
182             else {
183 25         771 $self->push_box_types(Box::Calc::BoxType->new($args));
184             }
185 28         916 return $self->get_box_type(-1);
186             }
187              
188              
189             =head2 box_type_categories()
190              
191             Returns an array reference of categories associated with the box types.
192              
193             =cut
194              
195             has box_type_categories => (
196             is => 'rw',
197             lazy => 1,
198             isa => 'ArrayRef',
199             default => sub {
200             my $self = shift;
201             my %categories = ();
202             foreach my $box_type (@{$self->box_types}) {
203             next if $box_type->category eq '';
204             $categories{$box_type->category} = 1;
205             }
206             return [sort keys %categories];
207             },
208             );
209              
210              
211             =head2 sort_box_types_by_volume()
212              
213             Sorts the list of C<box_types> by volume and then returns an array reference of that list.
214              
215             =over
216              
217             =item types
218              
219             Optional. Array ref of box types. Will call C<box_types> if not passed in.
220              
221             =back
222              
223             =cut
224              
225             sub sort_box_types_by_volume {
226 17     17 1 42 my $self = shift;
227 17   33     483 my $types = shift || $self->box_types;
228 17         35 my @sorted = sort { ($a->volume) <=> ($b->volume ) } @{$types};
  61         1528  
  17         58  
229 17         76 return \@sorted;
230             }
231              
232             =head2 determine_viable_box_types(category)
233              
234             Given the list of C<items> and the list of C<box_types> this method rules out box types that cannot hold the largest item, and returns the list of box types that will work sorted by volume.
235              
236             =over
237              
238             =item category
239              
240             Optional. If this is specified, it will match this category name to the categories attached to the boxes and only provide a list of boxes that match that category.
241              
242             =back
243              
244             =cut
245              
246             sub determine_viable_box_types {
247 16     16 1 1493 my ($self, $category) = @_;
248 16         30 my ($item_x, $item_y, $item_z) = sort {$b <=> $a} @{$self->find_max_dimensions_of_items};
  48         108  
  16         513  
249 16         30 my @viable;
250 16         32 foreach my $box_type (@{$self->sort_box_types_by_volume}) {
  16         77  
251 52 100       112 if (defined $category) {
252 18 100       426 next unless $category eq $box_type->category;
253             }
254 39         58 my ($box_type_x, $box_type_y, $box_type_z) = @{$box_type->dimensions};
  39         1054  
255 39 50 66     221 if ($item_x <= $box_type_x && $item_y <= $box_type_y && $item_z <= $box_type_z) {
      66        
256 33         88 push @viable, $box_type;
257             }
258             }
259 16 100       59 unless (scalar @viable) {
260 1         8 $log->fatal('There are no box types that can fit the items.');
261 1         13 ouch 'no viable box types', 'There are no box types that can fit the items. ('.join(', ', $item_x, $item_y, $item_z).')', [$item_x, $item_y, $item_z];
262             }
263 15         68 return \@viable;
264             }
265              
266             =head2 items()
267              
268             Returns an array reference of the L<Box::Calc::Item>s registered.
269              
270             =head2 count_items()
271              
272             Returns the number of L<Box::Calc::Item>s registered.
273              
274             =head2 get_item(index)
275              
276             Returns a specific L<Box::Calc::Item>.
277              
278             =over
279              
280             =item index
281              
282             The array index of the item as it was registered.
283              
284             =back
285              
286             =cut
287              
288             has items => (
289             is => 'rw',
290             isa => 'ArrayRef[Box::Calc::Item]',
291             default => sub { [] },
292             traits => ['Array'],
293             handles => {
294             push_items => 'push',
295             count_items => 'count',
296             get_item => 'get',
297             }
298             );
299              
300             =head2 add_item(quantity, params)
301              
302             Registers a new item. Returns the new item registered.
303              
304             =over
305              
306             =item quantity
307              
308             How many copies of this item should be included in the package?
309              
310             =item params
311              
312             The constructor parameters for the L<Box::Calc::Item>.
313              
314             =back
315              
316             =cut
317              
318             sub add_item {
319 26     26 1 9274 my ($self, $quantity, @params) = @_;
320 26         793 my $item = Box::Calc::Item->new(@params);
321 26         93 for (1..$quantity) {
322 10589         296978 $self->push_items($item);
323             }
324 26         833 return $self->get_item(-1);
325             }
326              
327             =head2 load(payload)
328              
329             Allows the loading of an entire dataset.
330              
331             =over
332              
333             =item payload
334              
335             A hash reference containing the output of the C<dump> method, with two exceptions:
336              
337             =over
338              
339             =item *
340              
341             You can create a C<categories> element that is an array ref in each box type rather than creating duplicate box types for each category.
342              
343             =item *
344              
345             You can create a C<quantity> element in each item rather than creating duplicate items to represent the quantity.
346              
347             =back
348              
349             =back
350              
351             =cut
352              
353             sub load {
354 1     1 1 7 my ($self, $payload) = @_;
355             # note that we copy the box type and item to avoid modifying the original
356 1         3 foreach my $type (@{$payload->{box_types}}) {
  1         3  
357 7         10 $self->add_box_type(%{$type});
  7         28  
358             }
359 1         3 foreach my $item (@{$payload->{items}}) {
  1         3  
360 6   50     22 $self->add_item($item->{quantity} || 1, %{$item});
  6         19  
361             }
362             }
363              
364             =head2 dump()
365              
366             =cut
367              
368             sub dump {
369 2     2 1 7 my ($self) = @_;
370 2         6 my $payload = {};
371 2         3 foreach my $type (@{$self->box_types}) {
  2         55  
372 14         21 push @{$payload->{box_types}}, $type->describe;
  14         40  
373             }
374 2         5 foreach my $item (@{$self->items}) {
  2         47  
375 12         16 push @{$payload->{items}}, $item->describe;
  12         42  
376             }
377 2         6 return $payload;
378             }
379              
380              
381             =head2 sort_items_by_volume()
382              
383             Returns an array reference of the list of C<items> registered sorted by volume.
384              
385             =over
386              
387             =item items
388              
389             Optional. An array reference of items. Will call C<items> if not passed in.
390              
391             =back
392              
393             =cut
394              
395             sub sort_items_by_volume {
396 1     1 1 3 my $self = shift;
397 1   33     55 my $items = shift || $self->items;
398 1         2 my @sorted = sort { ($a->volume) <=> ($b->volume ) } @{$items};
  10         237  
  1         5  
399 1         4 return \@sorted;
400             }
401              
402             =head2 sort_items_by_zxy()
403              
404             Returns an array reference of the list of C<items> registered sorted by z, then x, then y, ascending.
405              
406             =over
407              
408             =item items
409              
410             Optional. An array reference of items. Will call C<items> if not passed in.
411              
412             =back
413              
414             =cut
415              
416             sub sort_items_by_zxy {
417 0     0 1 0 my $self = shift;
418 0   0     0 my $items = shift || $self->items;
419             my @sorted = sort {
420 0 0 0     0 $a->z <=> $b->z
421             || $a->x <=> $b->x
422             || $a->y <=> $b->y
423 0         0 } @{$items};
  0         0  
424 0         0 return \@sorted;
425             }
426              
427             =head2 sort_items_by_z_desc_A()
428              
429             Returns an array reference of the list of C<items> registered sorted by z DESC, then area DESC
430              
431             =over
432              
433             =item items
434              
435             Optional. An array reference of items. Will call C<items> if not passed in.
436              
437             =back
438              
439             =cut
440              
441             sub sort_items_by_z_desc_A {
442 0     0 1 0 my $self = shift;
443 0   0     0 my $items = shift || $self->items;
444 0         0 my @sorted = map { $_->[1] }
445             sort {
446             $b->[0]->{z} <=> $a->[0]->{z}
447             || $b->[0]->{A} <=> $a->[0]->{A}
448 0 0       0 }
449             ##Fetch Z and calculate A
450 0         0 map { [ { z=>$_->z, A=>$_->x*$_->y }, $_ ] } @{$items};
  0         0  
  0         0  
451 0         0 return \@sorted;
452             }
453              
454             =head2 sort_items_by_zA()
455              
456             Returns an array reference of the list of C<items> registered sorted by z ASC, then area DESC
457              
458             =over
459              
460             =item items
461              
462             Optional. An array reference of items. Will call C<items> if not passed in.
463              
464             =back
465              
466             =cut
467              
468             sub sort_items_by_zA {
469 10     10 1 20 my $self = shift;
470 10   66     326 my $items = shift || $self->items;
471 10552         13700 my @sorted = map { $_->[1] }
472             sort {
473             $a->[0]->{z} <=> $b->[0]->{z}
474             || $b->[0]->{A} <=> $a->[0]->{A}
475 10642 50       19392 }
476             ##Fetch Z and calculate A
477 10         28 map { [ { z=>$_->z, A=>$_->x*$_->y }, $_ ] } @{$items};
  10552         252858  
  10         137  
478 10         2304 return \@sorted;
479             }
480              
481             =head2 sort_items_by_Az()
482              
483             =over
484              
485             =item items
486              
487             Optional. An array reference of items. Will call C<items> if not passed in.
488              
489             =back
490              
491             Returns an array reference of the list of C<items> registered sorted by A DESC, then z ASC
492              
493             =cut
494              
495             sub sort_items_by_Az {
496 0     0 1 0 my $self = shift;
497 0   0     0 my $items = shift || $self->items;
498 0         0 my @sorted = map { $_->[1] }
499             sort {
500             $b->[0]->{A} <=> $a->[0]->{A}
501             || $a->[0]->{z} <=> $b->[0]->{z}
502 0 0       0 }
503             ##Fetch Z and calculate A
504 0         0 map { [ { z=>$_->z, A=>$_->x*$_->y }, $_ ] } @{$items};
  0         0  
  0         0  
505 0         0 return \@sorted;
506             }
507              
508             =head2 find_max_dimensions_of_items()
509              
510             Given the registered C<items>, returns the max C<x>, C<y>, and C<z> of all items registered as an array reference.
511              
512             =cut
513              
514             has find_max_dimensions_of_items => (
515             is => 'rw',
516             lazy => 1,
517             isa => 'ArrayRef',
518             clearer => 'clear_max_dimensions_of_items',
519             default => sub {
520             my $self = shift;
521             my $x = 0;
522             my $y = 0;
523             my $z = 0;
524             foreach my $item (@{$self->items}) {
525             my ($ex, $ey, $ez) = @{$item->dimensions};
526             $x = $ex if $ex > $x;
527             $y = $ey if $ey > $y;
528             $z = $ez if $ez > $z;
529             }
530             return [$x, $y, $z];
531             }
532             );
533              
534             =head2 boxes()
535              
536             Returns an array reference of the list of L<Box::Calc::Box>es needed to pack up the items.
537              
538             B<NOTE:> This will be empty until you call C<pack_items>.
539              
540             =head2 count_boxes()
541              
542             Returns the number of boxes needed to pack up the items.
543              
544             =head2 get_box(index)
545              
546             Fetches a specific box from the list of <boxes>.
547              
548             =over
549              
550             =item index
551              
552             The array index of the box you wish to fetc.
553              
554             =back
555              
556             =cut
557              
558             has boxes => (
559             is => 'rw',
560             isa => 'ArrayRef[Box::Calc::Box]',
561             default => sub { [] },
562             traits => ['Array'],
563             handles => {
564             push_boxes => 'push',
565             count_boxes => 'count',
566             get_box => 'get',
567             }
568             );
569              
570             =head2 reset_boxes()
571              
572             Deletes the list of C<boxes>.
573              
574             If you wish to rerun the packing you should use this to delete the list of C<boxes> first. This is handy if you needed to add an extra item or extra box type after you already ran C<pack_items>.
575              
576             =cut
577              
578             sub reset_boxes {
579 4     4 1 70005 my $self = shift;
580 4         191 $self->boxes([]);
581             }
582              
583             =head2 reset_items()
584              
585             Deletes the list of C<items>.
586              
587             For the sake of speed you may wish to reuse a L<Box::Calc> instance with the box types already pre-loaded. In that case you'll want to use this method to remove the items you've already registered. You'll probably also want to call C<reset_boxes>.
588              
589             =cut
590              
591             sub reset_items {
592 1     1 1 2 my $self = shift;
593 1         23 $self->items([]);
594 1         34 $self->clear_max_dimensions_of_items;
595             }
596              
597             =head2 make_box($box_type)
598              
599             Handy method to create new box using a specified
600             box type.
601              
602             =cut
603              
604             sub make_box {
605 31     31 1 101 my ($self, $box_type) = @_;
606 31         974 return Box::Calc::Box->new(
607             swap_xy => 1,
608             mail_service_name => $box_type->mail_service_name,
609             x => $box_type->x,
610             y => $box_type->y,
611             z => $box_type->z,
612             weight => $box_type->weight,
613             max_weight => $box_type->max_weight,
614             name => $box_type->name,
615             outer_x => $box_type->outer_x,
616             outer_y => $box_type->outer_y,
617             outer_z => $box_type->outer_z,
618             );
619             }
620              
621             =head2 find_tallest_z ( [ items ] )
622              
623             Determines the median of z across all items in the list.
624              
625             =over
626              
627             =item items
628              
629             An array reference of items. Optional. Defaults to C<items>.
630              
631             =back
632              
633             =cut
634              
635             sub find_tallest_z {
636 3     3 1 11 my $self = shift;
637 3   66     32 my $items = shift || $self->items;
638 3         6 return max map { $_->z } @{$items};
  165         3752  
  3         7  
639             }
640              
641              
642             =head2 stack_like_items( options )
643              
644             Stacks all like-sized items into stacks of C<stack_height> for denser packing. Could be used as an optimizer before running C<pack_items>.
645              
646             =over
647              
648             =item options
649              
650             A hash.
651              
652             =over
653              
654             =item items
655              
656             Optional. If not specified, will be the C<items> list.
657              
658             =item stack_height
659              
660             Optional. If not specified, will be determined by calling C<find_tallest_z>.
661              
662             =back
663              
664             =back
665              
666             =cut
667              
668             sub stack_like_items {
669 2     2 1 5 my ($self, %options) = @_;
670 2   33     64 my $items = $options{items} || $self->items;
671 2   33     9 my $stack_height = $options{stack_height} || $self->find_tallest_z($items);
672 2         5 my %like;
673 2         3 foreach my $item (@{$items}) {
  2         7  
674 110         140 push @{$like{$item->extent}}, $item;
  110         2691  
675             }
676 2         5 my @stacks;
677 2         7 foreach my $kind (values %like) {
678 6 50       9 if (scalar @{$kind} == 1) {
  6         15  
679 0         0 push @stacks, $kind->[0];
680             }
681             else {
682 6   50     173 my $items_per_stack = int($stack_height / $kind->[0]->z) || 1;
683 6         12 my $iterator = natatime($items_per_stack, @{$kind});
  6         32  
684 6         40 while (my @items = $iterator->()) {
685 12         19 my $count = scalar @items;
686 12 100       24 if ($count == 1) {
687 6         28 push @stacks, $items[0];
688             }
689             else {
690 6         8 my $item = $items[0];
691 6         162 push @stacks, Box::Calc::Item->new(
692             x => $item->x,
693             y => $item->y,
694             z => $item->z * $count,
695             weight => $item->weight * $count,
696             name => 'Stack of '.$count.' '.$item->name,
697             no_sort => 1,
698             );
699             }
700             }
701             }
702             }
703 2         18 return \@stacks;
704             }
705              
706             =head2 pack_items(options)
707              
708             Uses the list of C<box_types> and the list of C<items> to create the list of boxes to be packed. This method populates the C<boxes> list.
709              
710             =over
711              
712             =item options
713              
714             A hash.
715              
716             =over
717              
718             =item items
719              
720             Optional. If omitted the items list will be populated with whatever the current B<best> general purpose preprocessed item list is. Currently that is C<sort_items_by_zA>.
721              
722             =item category
723              
724             Optional. If this is specified, it will match this category name to the categories attached to the boxes and only pack in boxes that match that category.
725              
726             =back
727              
728             =back
729              
730             =cut
731              
732             sub pack_items {
733 10     10 1 765 my ($self, %options) = @_;
734 10         35 my $category = $options{category};
735 10   66     67 my $items = $options{items} || $self->sort_items_by_zA;
736 10         22 my $item_count = scalar(@{$items});
  10         25  
737 10         22 my @box_types = @{$self->determine_viable_box_types($category)};
  10         56  
738 10         26 my $countdown = scalar(@box_types);
739 10         28 BOXTYPE: foreach my $box_type (@box_types) {
740 19         547 $log->info("Box Type: ".$box_type->name);
741 19         86 $countdown--;
742 19         86 my $box = $self->make_box($box_type);
743 19         51 ITEM: foreach my $item (@{$items}) {
  19         54  
744 17295         382060 $log->info("Item: ".$item->name);
745            
746             # swap the item's x & y if it will make the item fit tighter
747 17295 50 33     412049 if ($item->x > 0 && $item->y > 0) {
748 17295         50501 $log->debug("Item's dimensions are not 0.");
749 17295 50 33     406766 if ($box->x >= $item->y && $box->y >= $item->x) { # see if the item would still fit in the box if it swapped
750 17295         43394 $log->debug('Item would still fit in the box if we rotated it.');
751 17295         393310 my $original_x_per_layer = int($box->x / $item->x);
752 17295         374019 my $original_y_per_layer = int($box->y / $item->y);
753 17295         26597 my $original_count_per_layer = $original_x_per_layer * $original_y_per_layer;
754 17295         361582 my $new_count_per_layer = int($box->x / $item->y) * int($box->y / $item->x);
755 17295 50 66     84739 if ( $new_count_per_layer > $original_count_per_layer # you can fit more items per layer in a swap
      66        
756             || $original_x_per_layer == 0 || $original_y_per_layer == 0 # if we keep it the current rotation we definitely won't fit, probably due to previous rotation
757             ) {
758 5         31 $log->info('Rotating '.$item->{name}.', because we can fit more per layer if we rotate.');
759 5         136 my $temp_x = $item->x;
760 5         123 $item->x($item->y);
761 5         151 $item->y($temp_x);
762             }
763             }
764             }
765             else {
766 0         0 $log->error('Item has a zero (0) dimension. That should not happen.');
767             }
768              
769             # pack the item into the box
770 17295 100       48455 unless ($box->pack_item($item)) {
771 21 100       79 if ($countdown) { # we still have other boxes to try
772 9         36 $log->info("moving to next box type");
773 9         303 next BOXTYPE;
774             }
775             else { # no more boxes to try, time for spanning
776 12 50       30 if (scalar(@{$self->boxes}) > $item_count) {
  12         345  
777 0         0 $log->warn("More boxes than items.");
778             #ouch 'more boxes than items', 'The number of boxes has exceded the number of items, which should never happen.';
779             }
780 12         48 $log->info("no more box types, spanning");
781 12         409 $self->push_boxes($box);
782 12         56 $box = $self->make_box($box_type);
783 12         47 redo ITEM;
784             }
785             }
786             }
787            
788             # we made it through our entire item list, yay!
789 10         49 $log->info("finished!");
790 10         714 $self->push_boxes($box);
791 10         228 last BOXTYPE;
792             }
793             }
794              
795             =head2 packing_list()
796              
797             Returns a data structure with all the item names and quantities packed into boxes. This can be used to generate manifests.
798              
799             [
800             { # box one
801             id => "xxx",
802             name => "big box",
803             weight => 30.1,
804             packing_list => {
805             "soda" => 3,
806             "apple" => 1,
807             "water bottle" => 2,
808             }
809             }
810             ]
811              
812             =cut
813              
814             sub packing_list {
815 2     2 1 64737 my $self = shift;
816 2         19 my @boxes;
817 2         6 foreach my $box (@{$self->boxes}) {
  2         90  
818 14         70 my ($weight, $list) = $box->packing_list;
819 14         356 push @boxes, {
820             id => $box->id,
821             name => $box->name,
822             weight => $weight,
823             packing_list => $list,
824             };
825             }
826 2         15 return \@boxes;
827             }
828              
829             =head2 packing_instructions()
830              
831             Returns a data structure with all the item names individually packed into rows, layers, and boxes. This can be used to build documentation on how to pack a set of boxes, and to generate a complete build history.
832              
833             [
834             { # box one
835             id => "xxx",
836             name => "big box",
837             layers => [
838             { # layer one
839             rows => [
840             { # row one
841             items => [
842             { # item one
843             name => "apple",
844             ...
845             },
846             ...
847             ],
848             },
849             ...
850             ],
851             ...
852             },
853             ],
854             },
855             ]
856              
857             =cut
858              
859             sub packing_instructions {
860 4     4 1 2288 my $self = shift;
861 4         13 my @boxes = map { $_->packing_instructions} @{ $self->boxes };
  16         111  
  4         133  
862 4         50 return \@boxes;
863             }
864              
865             =head1 TODO
866              
867             There are some additional optimizations that could be done to speed things up a bit. We might also be able to get a better fill percentage (less void space), although that's not really the intent of Box::Calc.
868              
869             =head1 PREREQS
870              
871             L<Moose>
872             L<Ouch>
873             L<Log::Any>
874             L<Data::GUID>
875              
876             =head1 SUPPORT
877              
878             =over
879              
880             =item Repository
881              
882             L<http://github.com/rizen/Box-Calc>
883              
884             =item Bug Reports
885              
886             L<http://github.com/rizen/Box-Calc/issues>
887              
888             =back
889              
890              
891             =head1 SEE ALSO
892              
893             Although these modules don't solve the same problem as this module, they may help you build something that does if Box::Calc doesn't quite help you do what you want.
894              
895             =over
896              
897             =item L<Algorithm::Knapsack>
898              
899             =item L<Algorithm::Bucketizer>
900              
901             =item L<Algorithm::Knap01DP>
902              
903             =back
904              
905             =head1 AUTHOR
906              
907             =over
908              
909             =item JT Smith <jt_at_plainblack_dot_com>
910              
911             =item Colin Kuskie <colink_at_plainblack_dot_com>
912              
913             =back
914              
915             =head1 LEGAL
916              
917             Box::Calc is Copyright 2012 Plain Black Corporation (L<http://www.plainblack.com>) and is licensed under the same terms as Perl itself.
918              
919             =cut
920              
921 7     7   21230 no Moose;
  7         20  
  7         75  
922             __PACKAGE__->meta->make_immutable;