Passed
Push — master ( e748d4...4fe23d )
by Doug
02:40
created

LayerPacker::packVerticallyInsideItemFootprint()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 9.0066

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 9
eloc 22
c 5
b 0
f 0
nc 10
nop 11
dl 0
loc 33
ccs 22
cts 23
cp 0.9565
crap 9.0066
rs 8.0555

1 Method

Rating   Name   Duplication   Size   Complexity  
A LayerPacker::isSameDimensions() 0 11 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * Box packing (3D bin packing, knapsack problem).
4
 *
5
 * @author Doug Wright
6
 */
7
declare(strict_types=1);
8
9
namespace DVDoug\BoxPacker;
10
11
use function array_merge;
12
use function iterator_to_array;
13
use function max;
14
use Psr\Log\LoggerAwareInterface;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\NullLogger;
17
use function sort;
18
19
/**
20
 * Layer packer.
21
 *
22
 * @internal
23
 * @author Doug Wright
24
 */
25
class LayerPacker implements LoggerAwareInterface
26
{
27
    /**
28
     * The logger instance.
29
     *
30
     * @var LoggerInterface
31
     */
32
    private $logger;
33
34
    /**
35
     * Box to pack items into.
36
     *
37
     * @var Box
38
     */
39
    private $box;
40
41
    /**
42
     * Whether the packer is in single-pass mode.
43
     *
44
     * @var bool
45
     */
46
    private $singlePassMode = false;
47
48
    /**
49
     * @var OrientatedItemFactory
50
     */
51
    private $orientatedItemFactory;
52
53
    /**
54
     * Constructor.
55
     */
56 70
    public function __construct(Box $box)
57
    {
58 70
        $this->box = $box;
59 70
        $this->logger = new NullLogger();
60
61 70
        $this->orientatedItemFactory = new OrientatedItemFactory($this->box);
62 70
        $this->orientatedItemFactory->setLogger($this->logger);
63 70
    }
64
65
    /**
66
     * Sets a logger.
67
     */
68 70
    public function setLogger(LoggerInterface $logger): void
69
    {
70 70
        $this->logger = $logger;
71 70
        $this->orientatedItemFactory->setLogger($logger);
72 70
    }
73
74 58
    public function setSinglePassMode(bool $singlePassMode): void
75
    {
76 58
        $this->singlePassMode = $singlePassMode;
77 58
        $this->orientatedItemFactory->setSinglePassMode($singlePassMode);
78 58
    }
79
80
    /**
81
     * Pack items into an individual vertical layer.
82
     */
83 70
    public function packLayer(ItemList &$items, PackedItemList $packedItemList, int $startX, int $startY, int $startZ, int $widthForLayer, int $lengthForLayer, int $depthForLayer, int $guidelineLayerDepth, bool $considerStability): PackedLayer
84
    {
85 70
        $layer = new PackedLayer();
86 70
        $x = $startX;
87 70
        $y = $startY;
88 70
        $z = $startZ;
89 70
        $rowLength = 0;
90 70
        $prevItem = null;
91 70
        $skippedItems = [];
92 70
        $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight();
93
94 70
        while ($items->count() > 0) {
95 70
            $itemToPack = $items->extract();
96
97
            //skip items that will never fit e.g. too heavy
98 70
            if ($itemToPack->getWeight() > $remainingWeightAllowed) {
99 8
                continue;
100
            }
101
102 70
            $orientatedItem = $this->orientatedItemFactory->getBestOrientation($itemToPack, $prevItem, $items, $widthForLayer - $x, $lengthForLayer - $y, $depthForLayer, $rowLength, $x, $y, $z, $packedItemList, $considerStability);
103
104 70
            if ($orientatedItem instanceof OrientatedItem) {
105 70
                $packedItem = PackedItem::fromOrientatedItem($orientatedItem, $x, $y, $z);
106 70
                $layer->insert($packedItem);
107 70
                $packedItemList->insert($packedItem);
108
109 70
                $rowLength = max($rowLength, $packedItem->getLength());
110 70
                $prevItem = $orientatedItem;
111
112
                //Figure out if we can stack items on top of this rather than side by side
113
                //e.g. when we've packed a tall item, and have just put a shorter one next to it.
114 70
                $stackableDepth = ($guidelineLayerDepth ?: $layer->getDepth()) - $packedItem->getDepth();
115 70
                if ($stackableDepth > 0) {
116 32
                    $stackedLayer = $this->packLayer($items, $packedItemList, $x, $y, $z + $packedItem->getDepth(), $x + $packedItem->getWidth(), $y + $packedItem->getLength(), $stackableDepth, $stackableDepth, $considerStability);
117 32
                    $layer->merge($stackedLayer);
118
                }
119
120
                //Having now placed an item, there is space *within the same row* along the length. Pack into that.
121 70
                if ($rowLength - $orientatedItem->getLength() > 0) {
122 20
                    $layer->merge($this->packLayer($items, $packedItemList, $x, $y + $orientatedItem->getLength(), $z, $widthForLayer, $rowLength, $depthForLayer, $layer->getDepth(), $considerStability));
123
                }
124
125 70
                $x += $packedItem->getWidth();
126 70
                $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight(); // remember may have packed additional items
127
128 70
                if ($items->count() === 0 && $skippedItems) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $skippedItems of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
129 12
                    $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
130 12
                    $skippedItems = [];
131
                }
132 70
                continue;
133
            }
134
135 62
            if ($items->count() > 0) { // skip for now, move on to the next item
136 52
                $this->logger->debug("doesn't fit, skipping for now");
137 52
                $skippedItems[] = $itemToPack;
138
                // abandon here if next item is the same, no point trying to keep going. Last time is not skipped, need that to trigger appropriate reset logic
139 52
                while ($items->count() > 1 && static::isSameDimensions($itemToPack, $items->top())) {
140 38
                    $skippedItems[] = $items->extract();
141
                }
142 52
                continue;
143
            }
144
145 62
            if ($x > $startX) {
146 62
                $this->logger->debug('No more fit in width wise, resetting for new row');
147 62
                $y += $rowLength;
148 62
                $x = $startX;
149 62
                $rowLength = 0;
150 62
                $skippedItems[] = $itemToPack;
151 62
                $items = ItemList::fromArray($skippedItems, true);
152 62
                $skippedItems = [];
153 62
                $prevItem = null;
154 62
                continue;
155
            }
156
157 56
            $this->logger->debug('no items fit, so starting next vertical layer');
158 56
            $skippedItems[] = $itemToPack;
159
160 56
            $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
161
162 56
            return $layer;
163
        }
164
165 70
        return $layer;
166
    }
167
168
    /**
169
     * Compare two items to see if they have same dimensions.
170
     */
171 44
    private static function isSameDimensions(Item $itemA, Item $itemB): bool
172
    {
173 44
        if ($itemA === $itemB) {
174 34
            return true;
175
        }
176 20
        $itemADimensions = [$itemA->getWidth(), $itemA->getLength(), $itemA->getDepth()];
177 20
        $itemBDimensions = [$itemB->getWidth(), $itemB->getLength(), $itemB->getDepth()];
178 20
        sort($itemADimensions);
179 20
        sort($itemBDimensions);
180
181 20
        return $itemADimensions === $itemBDimensions;
182
    }
183
}
184