diff --git a/docs/advanced-usage.rst b/docs/custom-constraints.rst similarity index 54% rename from docs/advanced-usage.rst rename to docs/custom-constraints.rst index 57a92306..aa262f90 100644 --- a/docs/advanced-usage.rst +++ b/docs/custom-constraints.rst @@ -1,69 +1,5 @@ -Advanced usage -============== - -Used / remaining space ----------------------- - -After packing it is possible to see how much physical space in each ``PackedBox`` is taken up with items, -and how much space was unused (air). This information might be useful to determine whether it would be useful to source -alternative/additional sizes of box. - -At a high level, the ``getVolumeUtilisation()`` method exists which calculates how full the box is as a percentage of volume. - -Lower-level methods are also available for examining this data in detail either using ``getUsed[Width|Length|Depth()]`` -(a hypothetical box placed around the items) or ``getRemaining[Width|Length|Depth()]`` (the difference between the dimensions of -the actual box and the hypothetical box). - -.. note:: - - BoxPacker will always try to pack items into the smallest box available - -Example - warning on a massively oversized box -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: php - - getVolumeUtilisation() < 20) { - // box is 80% air, log a warning - } - } - -Positional information ----------------------- -It is also possible to see the precise positional and dimensional information of each item as packed. This is exposed as x,y,z -co-ordinates from origin, alongside width/length/depth in the packed orientation. :: - - z y - | / - | / - | / - |/ - 0------x - -Example -^^^^^^^ - -.. code-block:: php - - getItems(); - foreach ($packedItems as $packedItem) { // $packedItem->getItem() is your own item object - echo $packedItem->getItem()->getDescription() . ' was packed at co-ordinate ' ; - echo '(' . $packedItem->getX() . ', ' . $packedItem->getY() . ', ' . $packedItem->getZ() . ') with '; - echo 'l' . $packedItem->getLength() . ', w' . $packedItem->getWidth() . ', d' . $packedItem->getDepth(); - echo PHP_EOL; - } - } - -Custom Constraints ------------------- +Custom constraints +================== For more advanced use cases where greater control over the contents of each box is required (e.g. legal limits on the number of hazardous items per box, or perhaps fragile items requiring an extra-strong outer box) you may implement the ``BoxPacker\ConstrainedPlacementItem`` @@ -74,14 +10,12 @@ As with all other library methods, the objects passed into this callback are you properties and methods to use when evaluating a constraint, not only those defined by the standard ``BoxPacker\Item`` interface. Example - only allow 2 batteries per box -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +---------------------------------------- .. code-block:: php addBox(new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000)); - $packer->addBox(new TestBox('Le grande box', 3000, 3000, 100, 100, 2960, 2960, 80, 10000)); + $packer->addBox( + new TestBox( + reference: 'Le petite box', + outerWidth: 300, + outerLength: 300, + outerDepth: 10, + emptyWeight: 10, + innerWidth: 296, + innerLength: 296, + innerDepth: 8, + maxWeight: 1000 + ) + ); + $packer->addBox( + new TestBox( + reference: 'Le grande box', + outerWidth: 3000, + outerLength: 3000, + outerDepth: 100, + emptyWeight: 100, + innerWidth: 2960, + innerLength: 2960, + innerDepth: 80, + maxWeight: 10000 + ) + ); /* * Add items to be packed - e.g. from shopping cart stored in user session. Again, the dimensional information * (and keep-flat requirement) would normally come from a DB */ - $packer->addItem(new TestItem('Item 1', 250, 250, 12, 200, true), 1); // item, quantity - $packer->addItem(new TestItem('Item 2', 250, 250, 12, 200, true), 2); - $packer->addItem(new TestItem('Item 3', 250, 250, 24, 200, false), 1); - - $packedBoxes = $packer->pack(); - - echo "These items fitted into " . count($packedBoxes) . " box(es)" . PHP_EOL; - foreach ($packedBoxes as $packedBox) { - $boxType = $packedBox->getBox(); // your own box object, in this case TestBox - echo "This box is a {$boxType->getReference()}, it is {$boxType->getOuterWidth()}mm wide, {$boxType->getOuterLength()}mm long and {$boxType->getOuterDepth()}mm high" . PHP_EOL; - echo "The combined weight of this box and the items inside it is {$packedBox->getWeight()}g" . PHP_EOL; - - echo "The items in this box are:" . PHP_EOL; - $packedItems = $packedBox->getItems(); - foreach ($packedItems as $packedItem) { // $packedItem->getItem() is your own item object, in this case TestItem - echo $packedItem->getItem()->getDescription() . PHP_EOL; - } - } + $packer->addItem( + item: new TestItem( + description: 'Item 1', + width: 250, + length: 250, + depth: 12, + weight: 200, + allowedRotation: Rotation::KeepFlat + ), + qty: 1 + ); + $packer->addItem( + item: new TestItem( + description: 'Item 2', + width: 250, + length: 250, + depth: 12, + weight: 200, + allowedRotation: Rotation::KeepFlat + ), + qty: 2 + ); + $packer->addItem( + item: new TestItem( + description: 'Item 3', + width: 250, + length: 250, + depth: 24, + weight: 200, + allowedRotation: Rotation::BestFit + ), + qty: 1 + ); Does a set of items fit into a particular box ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: php insert(new TestItem('Item 1', 297, 296, 2, 200, false)); - $items->insert(new TestItem('Item 2', 297, 296, 2, 500, false)); - $items->insert(new TestItem('Item 3', 296, 296, 4, 290, false)); + $items->insert( + new TestItem( + description: 'Item 1', + width: 297, + length: 296, + depth: 2, + weight: 200, + allowedRotation: Rotation::BestFit + ) + ); + $items->insert( + new TestItem( + description: 'Item 2', + width: 297, + length: 296, + depth: 2, + weight: 500, + allowedRotation: Rotation::BestFit + ) + ); + $items->insert( + new TestItem( + description: 'Item 3', + width: 296, + length: 296, + depth: 4, + weight: 290, + allowedRotation: Rotation::BestFit + ) + ); $volumePacker = new VolumePacker($box, $items); $packedBox = $volumePacker->pack(); //$packedBox->getItems() contains the items that fit diff --git a/docs/index.rst b/docs/index.rst index d3525d13..10214dce 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,5 +25,8 @@ BoxPacker is licensed under the `MIT license`_. sortation weight-distribution too-large-items - advanced-usage + positional-information + limited-supply-boxes + custom-constraints + used-remaining-space whatsnew diff --git a/docs/limited-supply-boxes.rst b/docs/limited-supply-boxes.rst new file mode 100644 index 00000000..7b7020ae --- /dev/null +++ b/docs/limited-supply-boxes.rst @@ -0,0 +1,13 @@ +Limited supply boxes +==================== + +In standard/basic use, BoxPacker will assume you have an adequate enough supply of each box type on hand to cover all +eventualities i.e. your warehouse will be very well stocked and the concept of "running low" is not applicable. + +However, if you only have limited quantities of boxes available and you have accurate stock control information, you can +feed this information into BoxPacker which will then take it into account so that it won't suggest a packing which would +take you into negative stock. + +To do this, have your box objects implement the ``BoxPacker\LimitedSupplyBox`` interface which has a single additional method +over the standard ``BoxPacker\Box`` namely ``getQuantityAvailable()``. The library will automatically detect this and +use the information accordingly. diff --git a/docs/positional-information.rst b/docs/positional-information.rst new file mode 100644 index 00000000..3ce126f3 --- /dev/null +++ b/docs/positional-information.rst @@ -0,0 +1,31 @@ +.. _positional_information: + +Positional information +====================== + +It is possible to see the precise positional and dimensional information of each item as packed. This is exposed as x,y,z +co-ordinates from origin, alongside width/length/depth in the packed orientation. :: + + z y + | / + | / + | / + |/ + 0------x + +Example: + +.. code-block:: php + + getItems(); + foreach ($packedItems as $packedItem) { // $packedItem->getItem() is your own item object + echo $packedItem->getItem()->getDescription() . ' was packed at co-ordinate ' ; + echo '(' . $packedItem->getX() . ', ' . $packedItem->getY() . ', ' . $packedItem->getZ() . ') with '; + echo 'l' . $packedItem->getLength() . ', w' . $packedItem->getWidth() . ', d' . $packedItem->getDepth(); + echo PHP_EOL; + } + } diff --git a/docs/rotation.rst b/docs/rotation.rst index 1b484b21..bc29dd13 100644 --- a/docs/rotation.rst +++ b/docs/rotation.rst @@ -9,7 +9,6 @@ BoxPacker gives you full control of how (or if) an individual item may be rotate Best fit ^^^^^^^^ - To allow an item to be placed in any orientation. .. code-block:: php @@ -27,7 +26,6 @@ To allow an item to be placed in any orientation. Keep flat ^^^^^^^^^ - For items that must be shipped "flat" or "this way up". .. code-block:: php diff --git a/docs/sortation.rst b/docs/sortation.rst index 6e16f13b..99d676fe 100644 --- a/docs/sortation.rst +++ b/docs/sortation.rst @@ -1,3 +1,5 @@ +.. _sortation: + Sortation ========= diff --git a/docs/too-large-items.rst b/docs/too-large-items.rst index 61a0be1b..f6c4ceb2 100644 --- a/docs/too-large-items.rst +++ b/docs/too-large-items.rst @@ -8,7 +8,10 @@ There is no attempt made to handle/recover from them internally. This includes the case where there are no boxes large enough to pack a particular item. The normal operation of the Packer class is to throw an ``NoBoxesAvailableException``. If your application has well-defined logging and monitoring it may be sufficient to simply allow the exception to bubble up to your generic handling layer and handle like any other runtime failure. -Alternatively, you may wish to catch it explicitly and have domain-specific handling logic e.g. +Applications that do that can make an assumption that if no exceptions were thrown, then all items were successfully +placed into a box. + +Alternatively, you might wish to catch the exception explicitly and have domain-specific handling logic e.g. .. code-block:: php @@ -24,9 +27,9 @@ Alternatively, you may wish to catch it explicitly and have domain-specific hand $packer->addBox(new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000)); $packer->addBox(new TestBox('Le grande box', 3000, 3000, 100, 100, 2960, 2960, 80, 10000)); - $packer->addItem(new TestItem('Item 1', 2500, 2500, 20, 2000, true)); - $packer->addItem(new TestItem('Item 2', 25000, 2500, 20, 2000, true)); - $packer->addItem(new TestItem('Item 3', 2500, 2500, 20, 2000, true)); + $packer->addItem(new TestItem('Item 1', 2500, 2500, 20, 2000, Rotation::BestFit)); + $packer->addItem(new TestItem('Item 2', 25000, 2500, 20, 2000, Rotation::BestFit)); + $packer->addItem(new TestItem('Item 3', 2500, 2500, 20, 2000, Rotation::BestFit)); $packedBoxes = $packer->pack(); } catch (NoBoxesAvailableException $e) { @@ -34,8 +37,9 @@ Alternatively, you may wish to catch it explicitly and have domain-specific hand // pause dispatch, email someone or any other handling of your choosing } -For some applications the ability/requirement to do their own handling of this case may not be wanted or may even be -problematic, e.g. if some items being too large and requiring special handling is a normal situation for that particular business. +However, an ``Exception`` is for exceptional situations and for some businesses, some items being too large and thus +requiring special handling might be considered a normal everyday situation. For these applications, having an +``Exception`` thrown which interrupts execution might be not be wanted or be considered problematic. BoxPacker also supports this workflow with the ``InfalliblePacker``. This class extends the base ``Packer`` and automatically handles any ``NoBoxesAvailableException``. It can be used like this: @@ -52,9 +56,11 @@ handles any ``NoBoxesAvailableException``. It can be used like this: $packer->addBox(new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000)); $packer->addBox(new TestBox('Le grande box', 3000, 3000, 100, 100, 2960, 2960, 80, 10000)); - $packer->addItem(new TestItem('Item 1', 2500, 2500, 20, 2000, true)); - $packer->addItem(new TestItem('Item 2', 25000, 2500, 20, 2000, true)); - $packer->addItem(new TestItem('Item 3', 2500, 2500, 20, 2000, true)); + $packer->addItem(new TestItem('Item 1', 2500, 2500, 20, 2000, Rotation::BestFit)); + $packer->addItem(new TestItem('Item 2', 25000, 2500, 20, 2000, Rotation::BestFit)); + $packer->addItem(new TestItem('Item 3', 2500, 2500, 20, 2000, Rotation::BestFit)); $packedBoxes = $packer->pack(); //same as regular Packer + + // It is *very* important to check this is an empty list (or not) when exceptions are disabled! $unpackedItems = $packer->getUnpackedItems(); diff --git a/docs/used-remaining-space.rst b/docs/used-remaining-space.rst new file mode 100644 index 00000000..f7bc160c --- /dev/null +++ b/docs/used-remaining-space.rst @@ -0,0 +1,30 @@ +Used / remaining space +====================== + +After packing it is possible to see how much physical space in each ``PackedBox`` is taken up with items, +and how much space was unused (air). This information might be useful to determine whether it would be useful to source +alternative/additional sizes of box. + +At a high level, the ``getVolumeUtilisation()`` method exists which calculates how full the box is as a percentage of volume. + +Lower-level methods are also available for examining this data in detail either using ``getUsed[Width|Length|Depth()]`` +(a hypothetical box placed around the items) or ``getRemaining[Width|Length|Depth()]`` (the difference between the dimensions of +the actual box and the hypothetical box). + +.. note:: + + BoxPacker will try to pack items into the smallest box available + +Example - warning on a massively oversized box +---------------------------------------------- + +.. code-block:: php + + getVolumeUtilisation() < 20) { + // box is 80% air, log a warning + } + }