Skip to content

Commit

Permalink
Merge branch '2.x-dev' into 1.x-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
dvdoug committed Jan 1, 2018
2 parents 42e7688 + 06e1a72 commit 9df4ff9
Show file tree
Hide file tree
Showing 33 changed files with 5,497 additions and 10,998 deletions.
28 changes: 24 additions & 4 deletions .scrutinizer.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
checks:
php: true
php: true

tools:
external_code_coverage:
timeout: 3600 # Timeout in seconds.
filter:
excluded_paths:
- 'features/'
- 'tests/'

build:
environment:
# Languages
php:
version: "7.1"

tests:
override:
-
command: 'vendor/bin/phpunit --coverage-clover=build/phpunit.clover --exclude-group efficiency'
coverage:
file: 'build/phpunit.clover'
format: 'clover'
-
command: 'vendor/bin/behat'
coverage:
file: 'build/behat.clover'
format: 'clover'
7 changes: 0 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,3 @@ script:
else
php vendor/bin/phpunit;
fi;
after_script:
- |
if [ "$TRAVIS_PHP_VERSION" = "7.1" ]; then
wget https://scrutinizer-ci.com/ocular.phar;
php ocular.phar code-coverage:upload --format=php-clover coverage.clover;
fi;
20 changes: 20 additions & 0 deletions behat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
default:
extensions:
LeanPHP\Behat\CodeCoverage\Extension:
auth: ~
drivers:
- local
filter:
forceCoversAnnotation: false
mapTestClassNameToCoveredClassName: false
whitelist:
addUncoveredFilesFromWhitelist: true
processUncoveredFilesFromWhitelist: true
include:
directories:
'src':
prefix: ''
report:
format: clover
options:
target: build/behat.clover
7 changes: 4 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
"psr/log": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35||^5.0||^6.0",
"monolog/monolog": "^1.11",
"squizlabs/php_codesniffer": "^2.5"
"behat/behat": "^3.4",
"leanphp/behat-code-coverage": "^3.0",
"monolog/monolog": "^1.0",
"phpunit/phpunit": "^4.8.35||^5.0||^6.0"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ you to pass in these objects directly too.

To accommodate the wide variety of possible object types, the library defines two interfaces ``BoxPacker\Item`` and
``BoxPacker\Box`` which define methods for retrieving the required dimensional data - e.g. ``getWidth()``. There's a good chance
you may already have these defined.
you may already have at least some of these defined.

If you do happen to have methods defined with those names already, **and they are incompatible with the interface expectations**,
then this will be only case where some kind of wrapper object would be needed.
Expand Down
8 changes: 5 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Welcome to BoxPacker's documentation!
=====================================

BoxPacker is an implementation of the 3D bin packing/knapsack problem i.e. given a list of items, how many boxes do you need to
BoxPacker is an implementation of the "4D" bin packing/knapsack problem i.e. given a list of items, how many boxes do you need to
fit them all in.

Especially useful for e.g. e-commerce contexts when you need to know box size/weight to calculate shipping costs, or
Expand All @@ -15,8 +15,7 @@ BoxPacker is licensed under the `MIT license`_.
.. warning::

You are reading the documentation for BoxPacker v1. Although still supported with occasional backports from newer versions,
all users are recommended to upgrade to v2 which removes the "always keep flat" limitation on items.

all users are recommended to upgrade to v2/v3 which removes the "always keep flat" limitation on items.

.. _NP-hard problem: http://en.wikipedia.org/wiki/Bin_packing_problem
.. _MIT license: https://github.com/dvdoug/BoxPacker/blob/master/license.txt
Expand All @@ -30,3 +29,6 @@ BoxPacker is licensed under the `MIT license`_.
principles
getting-started
advanced-usage
weight-distribution
advanced-usage
whatsnew
3 changes: 2 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ BoxPacker is compatible with all versions of PHP 5.4+ (including PHP 7 and HHVM)

Versioning
----------
BoxPacker follows `Semantic Versioning`_
BoxPacker follows `Semantic Versioning`_. For details about differences between releases please see `What's new`_


.. _Composer: https://getcomposer.org
.. _GitHub: https://github.com/dvdoug/BoxPacker/releases
.. _Semantic Versioning: http://semver.org/
.. _What's new: whatsnew.html
33 changes: 33 additions & 0 deletions docs/weight-distribution.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Weight distribution
===================

If you are shipping a large number of items to a single customer as many businesses do, it might be that more than one box is
required to accommodate all of the items. A common scenario which you'll have probably encountered when receiving your own
deliveries is that the first box(es) will be absolutely full as the warehouse operative will have tried to fit in as much as
possible. The last box by comparison will be virtually empty and mostly filled with protective inner packing.

There's nothing intrinsically wrong with this, but it can be a bit annoying for e.g. couriers and customers to receive e.g.
a 20kg box which requires heavy lifting alongside a similarly sized box that weighs hardly anything at all. If you have to send
two boxes anyway, it would be much better in such a situation to have e.g. an 11kg box and a 10kg box instead.

Happily, this smoothing out of weight is handled automatically for you by BoxPacker - once the initial dimension-only packing
is completed, a second pass is made that reallocates items from heavier boxes into any lighter ones that have space.

For most use-cases the benefits are worth the extra computation time - however if a single "packing" for your scenarios
involves a very large number of permutations e.g. thousands of items, you may wish to tune this behaviour.

By default, the weight distribution pass is made whenever the items fit into 12 boxes or less. To reduce (or increase) the
threshold, call ``setMaxBoxesToBalanceWeight()``

.. code-block:: php
<?php
use DVDoug\BoxPacker\Packer;
$packer = new Packer();
$packer->setMaxBoxesToBalanceWeight(3);
.. note::

A threshold value of either 0 or 1 will disable the weight distribution pass completely
7 changes: 7 additions & 0 deletions docs/whatsnew.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
What's new / Upgrading
======================

.. note::

A full changelog, including changes in versions not yet
released is available from https://github.com/dvdoug/BoxPacker/blob/master/CHANGELOG.md
46 changes: 46 additions & 0 deletions features/BoxPacker.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Feature: BoxPacker
In order to make logistics easier
As a developer
I need to be able to find out how a set of items can fit into a set of boxes

Scenario: Fitting small items into a small box only
Given there is a box "Small", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
And there is a box "Large", which has external dimensions 3000mm w × 3000mm l × 100mm d × 100g and internal dimensions 2960mm w × 2960mm l × 80mm d and has a max weight of 10000g
When I add 3 x keep flat "Small Item" with dimensions 250mm w × 250mm l × 2mm d × 200g
And I do a packing
Then I should have 1 boxes of type "Small"
And I should have 0 boxes of type "Large"

Scenario: Fitting large items into a large box only
Given there is a box "Small", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
And there is a box "Large", which has external dimensions 3000mm w × 3000mm l × 100mm d × 100g and internal dimensions 2960mm w × 2960mm l × 80mm d and has a max weight of 10000g
When I add 3 x keep flat "Large Item" with dimensions 2500mm w × 2500mm l × 20mm d × 2000g
And I do a packing
Then I should have 0 boxes of type "Small"
And I should have 1 boxes of type "Large"

Scenario: Fitting mixed size items into a mix of box sizes
Given there is a box "Small", which has external dimensions 600mm w × 600mm l × 10mm d × 10g and internal dimensions 596mm w × 596mm l × 8mm d and has a max weight of 1000g
And there is a box "Large", which has external dimensions 3000mm w × 3000mm l × 50mm d × 100g and internal dimensions 2960mm w × 2960mm l × 40mm d and has a max weight of 10000g
When I add 1 x keep flat "Small Item" with dimensions 550mm w × 550mm l × 2mm d × 500g
When I add 4 x keep flat "Large Item" with dimensions 2500mm w × 2500mm l × 20mm d × 500g
And I do a packing
Then I should have 1 boxes of type "Small"
And I should have 2 boxes of type "Large"

Scenario: Simple stacking
Given there is a box "Small", which has external dimensions 292mm w × 336mm l × 60mm d × 10g and internal dimensions 292mm w × 336mm l × 60mm d and has a max weight of 9000g
And there is a box "Large", which has external dimensions 421mm w × 548mm l × 335mm d × 100g and internal dimensions 421mm w × 548mm l × 335mm d and has a max weight of 10000g
When I add 1 x keep flat "Small Item" with dimensions 226mm w × 200mm l × 40mm d × 440g
When I add 1 x keep flat "Large Item" with dimensions 200mm w × 200mm l × 155mm d × 1660g
And I do a packing
Then I should have 0 boxes of type "Small"
And I should have 1 boxes of type "Large"

Scenario: Making sure whatever bug caused issue #3 doesn't come back
Given there is a box "Box A", which has external dimensions 51mm w × 33mm l × 33mm d × 1g and internal dimensions 51mm w × 33mm l × 33mm d and has a max weight of 1g
And there is a box "Box B", which has external dimensions 50mm w × 40mm l × 40mm d × 1g and internal dimensions 50mm w × 40mm l × 40mm d and has a max weight of 1g
When I add 6 x keep flat "Item" with dimensions 28mm w × 19mm l × 9mm d × 0g
And I do a packing
Then I should have 1 boxes of type "Box A"
And I should have 0 boxes of type "Box B"
122 changes: 122 additions & 0 deletions features/SingleBoxPacking.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
Feature: BoxPacker
In order to make logistics easier
As a developer
I need to be able to find out how a set of items can fit into a box

Scenario: Packing 3 items that fit easily
Given the box "Box", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
When I add 3 x "Small Item" with dimensions 250mm w × 250mm l × 2mm d × 200g
And I do a volume-only packing
Then the packed box should have 3 items of type "Small Item"

Scenario: Packing 3 items that fit exactly
Given the box "Box", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
When I add 2 x "Small Item" with dimensions 250mm w × 250mm l × 2mm d × 200g
When I add 1 x "Medium Item" with dimensions 250mm w × 250mm l × 4mm d × 200g
And I do a volume-only packing
Then the packed box should have 2 items of type "Small Item"
And the packed box should have 1 items of type "Medium Item"

Scenario: Packing 3 items that would fit exactly but break the weight limit
Given the box "Box", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
When I add 1 x "Small Item" with dimensions 250mm w × 250mm l × 2mm d × 200g
When I add 1 x "Medium Item" with dimensions 250mm w × 250mm l × 2mm d × 400g
When I add 1 x "Heavy Item" with dimensions 250mm w × 250mm l × 2mm d × 500g
And I do a volume-only packing
Then the packed box should have 0 items of type "Small Item"
And the packed box should have 1 items of type "Medium Item"
And the packed box should have 1 items of type "Heavy Item"

Scenario: Packing 2 items that fit exactly as long as they aren't rotated
Given the box "Box", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
When I add 1 x "Small Item" with dimensions 296mm w × 148mm l × 2mm d × 200g
When I add 1 x "Medium Item" with dimensions 296mm w × 148mm l × 4mm d × 500g
And I do a volume-only packing
Then the packed box should have 1 items of type "Small Item"
And the packed box should have 1 items of type "Medium Item"

Scenario: Packing 3 items where 2 are oversized
Given the box "Box", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
When I add 1 x "Item A" with dimensions 297mm w × 296mm l × 2mm d × 200g
When I add 1 x "Item B" with dimensions 297mm w × 296mm l × 2mm d × 500g
When I add 1 x "Item C" with dimensions 296mm w × 296mm l × 4mm d × 290g
And I do a volume-only packing
Then the packed box should have 0 items of type "Item A"
And the packed box should have 0 items of type "Item B"
And the packed box should have 1 items of type "Item C"

Scenario: Packing 2 items that fit exactly side by side after rotating
Given the box "Box", which has external dimensions 300mm w × 500mm l × 10mm d × 10g and internal dimensions 296mm w × 496mm l × 8mm d and has a max weight of 1000g
When I add 1 x "Item A" with dimensions 296mm w × 248mm l × 8mm d × 200g
When I add 1 x "Item B" with dimensions 248mm w × 296mm l × 8mm d × 200g
And I do a volume-only packing
Then the packed box should have 1 items of type "Item A"
And the packed box should have 1 items of type "Item B"

Scenario: Packing 3 items with 2 fitting exactly side by side after rotating, and then 1 stacked (perfect fit)
Given the box "Box", which has external dimensions 300mm w × 300mm l × 10mm d × 10g and internal dimensions 296mm w × 296mm l × 8mm d and has a max weight of 1000g
When I add 1 x "Item A" with dimensions 248mm w × 148mm l × 4mm d × 200g
When I add 1 x "Item B" with dimensions 148mm w × 248mm l × 4mm d × 200g
When I add 1 x "Item C" with dimensions 296mm w × 296mm l × 4mm d × 200g
And I do a volume-only packing
Then the packed box should have 1 items of type "Item A"
And the packed box should have 1 items of type "Item B"
And the packed box should have 1 items of type "Item C"

Scenario: Packing 3 items with 2 fitting exactly side by side after rotating, and then 1 stacked (with overhang)
Given the box "Box", which has external dimensions 250mm w × 250mm l × 10mm d × 10g and internal dimensions 248mm w × 248mm l × 8mm d and has a max weight of 1000g
When I add 1 x "Item A" with dimensions 200mm w × 200mm l × 4mm d × 200g
When I add 2 x "Item B" with dimensions 110mm w × 110mm l × 4mm d × 200g
And I do a volume-only packing
Then the packed box should have 1 items of type "Item A"
And the packed box should have 2 items of type "Item B"

Scenario: Packing an item which requires 3D rotation to fit
Given the box "Box", which has external dimensions 100mm w × 100mm l × 300mm d × 10g and internal dimensions 100mm w × 100mm l × 300mm d and has a max weight of 1500g
When I add 1 x "Item A" with dimensions 150mm w × 50mm l × 50mm d × 20g
And I do a volume-only packing
Then the packed box should have 1 items of type "Item A"

Scenario: Not 3D rotating unnecessarily (issue #53)
Given the box "Box", which has external dimensions 500mm w × 1000mm l × 500mm d × 0g and internal dimensions 500mm w × 1000mm l × 500mm d and has a max weight of 0g
When I add 1 x "Item A" with dimensions 500mm w × 500mm l × 500mm d × 0g
When I add 2 x "Item B" with dimensions 500mm w × 500mm l × 250mm d × 0g
And I do a volume-only packing
Then the packed box should have 1 items of type "Item A"
Then the packed box should have 2 items of type "Item B"

Scenario: Making sure whatever bug caused issue #75 doesn't come back
Given the box "Box", which has external dimensions 20mm w × 12mm l × 10mm d × 0g and internal dimensions 20mm w × 12mm l × 10mm d and has a max weight of 2500g
When I add 2 x "Item A" with dimensions 12mm w × 12mm l × 5mm d × 8g
When I add 2 x "Item B" with dimensions 8mm w × 12mm l × 5mm d × 8g
And I do a volume-only packing
Then the packed box should have 2 items of type "Item A"
Then the packed box should have 2 items of type "Item B"

Scenario: Test identical items that could be fit side by side actually do (issue #89)
Given the box "SRA3 Sheet", which has external dimensions 450mm w × 320mm l × 1mm d × 0g and internal dimensions 450mm w × 320mm l × 1mm d and has a max weight of 0g
When I add 4 x "A5 Sheet" with dimensions 148mm w × 210mm l × 1mm d × 0g
And I do a volume-only packing
Then the packed box should have 4 items of type "A5 Sheet"

Scenario: Test smaller cubes successfully fit into a larger cube (issue #9)
Given the box "Box", which has external dimensions 24mm w × 24mm l × 24mm d × 24g and internal dimensions 24mm w × 24mm l × 24mm d and has a max weight of 100g
When I add 64 x keep flat "Item" with dimensions 6mm w × 6mm l × 6mm d × 1g
And I do a volume-only packing
Then the packed box should have 64 items of type "Item"

Scenario: Test shallower items can be stacked alongside a taller one (issue #11)
Given the box "Box", which has external dimensions 4mm w × 4mm l × 4mm d × 4g and internal dimensions 4mm w × 4mm l × 4mm d and has a max weight of 100g
When I add 2 x keep flat "Tall Item" with dimensions 2mm w × 2mm l × 4mm d × 1g
When I add 32 x keep flat "Shallow Item" with dimensions 1mm w × 1mm l × 1mm d × 1g
And I do a volume-only packing
Then the packed box should have 2 items of type "Tall Item"
And the packed box should have 32 items of type "Shallow Item"

Scenario: Test shallower items can be stacked alongside a taller one (issue #13)
Given the box "Box", which has external dimensions 12mm w × 12mm l × 12mm d × 12g and internal dimensions 10mm w × 10mm l × 10mm d and has a max weight of 1000g
When I add 2 x keep flat "Item A" with dimensions 5mm w × 3mm l × 2mm d × 2g
When I add 1 x keep flat "Item B" with dimensions 3mm w × 3mm l × 3mm d × 3g
And I do a volume-only packing
Then the packed box should have 2 items of type "Item A"
And the packed box should have 1 items of type "Item B"
Loading

0 comments on commit 9df4ff9

Please sign in to comment.