Passed
Push — master ( 2e0db4...02b4d3 )
by Doug
02:32
created

LayerPacker::setBoxIsRotated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
15
use function array_merge;
16
use function iterator_to_array;
17
use function max;
18
use function sort;
19
20
/**
21
 * Layer packer.
22
 * @internal
23
 */
24
class LayerPacker implements LoggerAwareInterface
25
{
26
    private LoggerInterface $logger;
27
28
    private bool $singlePassMode = false;
29
30
    private readonly OrientatedItemFactory $orientatedItemFactory;
31
32
    private bool $beStrictAboutItemOrdering = false;
33
34
    private bool $isBoxRotated = false;
35
36 92
    public function __construct(private readonly Box $box)
37
    {
38 92
        $this->logger = new NullLogger();
39
40 92
        $this->orientatedItemFactory = new OrientatedItemFactory($this->box);
0 ignored issues
show
Bug introduced by
The property orientatedItemFactory is declared read-only in DVDoug\BoxPacker\LayerPacker.
Loading history...
41 92
        $this->orientatedItemFactory->setLogger($this->logger);
42
    }
43
44
    /**
45
     * Sets a logger.
46
     */
47 92
    public function setLogger(LoggerInterface $logger): void
48
    {
49 92
        $this->logger = $logger;
50 92
        $this->orientatedItemFactory->setLogger($logger);
51
    }
52
53 22
    public function setSinglePassMode(bool $singlePassMode): void
54
    {
55 22
        $this->singlePassMode = $singlePassMode;
56 22
        $this->orientatedItemFactory->setSinglePassMode($singlePassMode);
57
    }
58
59 92
    public function setBoxIsRotated(bool $boxIsRotated): void
60
    {
61 92
        $this->isBoxRotated = $boxIsRotated;
62 92
        $this->orientatedItemFactory->setBoxIsRotated($boxIsRotated);
63
    }
64
65 39
    public function beStrictAboutItemOrdering(bool $beStrict): void
66
    {
67 39
        $this->beStrictAboutItemOrdering = $beStrict;
68
    }
69
70
    /**
71
     * Pack items into an individual vertical layer.
72
     */
73 92
    public function packLayer(ItemList &$items, PackedItemList $packedItemList, int $startX, int $startY, int $startZ, int $widthForLayer, int $lengthForLayer, int $depthForLayer, int $guidelineLayerDepth, bool $considerStability): PackedLayer
0 ignored issues
show
Bug introduced by
The type DVDoug\BoxPacker\PackedItemList was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
74
    {
75 92
        $layer = new PackedLayer();
76 92
        $x = $startX;
77 92
        $y = $startY;
78 92
        $z = $startZ;
79 92
        $rowLength = 0;
80 92
        $prevItem = null;
81 92
        $skippedItems = [];
82 92
        $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight();
83
84 92
        while ($items->count() > 0) {
85 92
            $itemToPack = $items->extract();
86
87
            // skip items that will never fit e.g. too heavy
88 92
            if ($itemToPack->getWeight() > $remainingWeightAllowed) {
89 18
                continue;
90
            }
91
92 90
            $orientatedItem = $this->orientatedItemFactory->getBestOrientation($itemToPack, $prevItem, $items, $widthForLayer - $x, $lengthForLayer - $y, $depthForLayer, $rowLength, $x, $y, $z, $packedItemList, $considerStability);
93
94 90
            if ($orientatedItem instanceof OrientatedItem) {
95 89
                $packedItem = PackedItem::fromOrientatedItem($orientatedItem, $x, $y, $z);
96 89
                $layer->insert($packedItem);
97 89
                $packedItemList->insert($packedItem);
98
99 89
                $rowLength = max($rowLength, $packedItem->getLength());
100 89
                $prevItem = $orientatedItem;
101
102
                // Figure out if we can stack items on top of this rather than side by side
103
                // e.g. when we've packed a tall item, and have just put a shorter one next to it.
104 89
                $stackableDepth = ($guidelineLayerDepth ?: $layer->getDepth()) - $packedItem->getDepth();
105 89
                if ($stackableDepth > 0) {
106 30
                    $stackedLayer = $this->packLayer($items, $packedItemList, $x, $y, $z + $packedItem->getDepth(), $x + $packedItem->getWidth(), $y + $packedItem->getLength(), $stackableDepth, $stackableDepth, $considerStability);
107 30
                    $layer->merge($stackedLayer);
108
                }
109
110 89
                $x += $packedItem->getWidth();
111 89
                $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight(); // remember may have packed additional items
112
113 89
                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...
114 16
                    $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
115 16
                    $skippedItems = [];
116
                }
117 89
                continue;
118
            }
119
120 81
            if (!$this->beStrictAboutItemOrdering && $items->count() > 0) { // skip for now, move on to the next item
121 70
                $this->logger->debug("doesn't fit, skipping for now");
122 70
                $skippedItems[] = $itemToPack;
123
                // 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
124 70
                while ($items->count() > 1 && self::isSameDimensions($itemToPack, $items->top())) {
125 32
                    $skippedItems[] = $items->extract();
126
                }
127 70
                continue;
128
            }
129
130 81
            if ($x > $startX) {
131
                // Having now placed items, there is space *within the same row* along the length. Pack into that.
132 80
                $this->logger->debug('No more fit in width wise, packing along remaining length');
133 80
                $layer->merge($this->packLayer($items, $packedItemList, $x, $y + $rowLength, $z, $widthForLayer, $lengthForLayer - $rowLength, $depthForLayer, $layer->getDepth(), $considerStability));
134
135 80
                $this->logger->debug('No more fit in width wise, resetting for new row');
136 80
                $y += $rowLength;
137 80
                $x = $startX;
138 80
                $rowLength = 0;
139 80
                $skippedItems[] = $itemToPack;
140 80
                $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
141 80
                $skippedItems = [];
142 80
                $prevItem = null;
143 80
                continue;
144
            }
145
146 71
            $this->logger->debug('no items fit, so starting next vertical layer');
147 71
            $skippedItems[] = $itemToPack;
148
149 71
            $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
150
151 71
            return $layer;
152
        }
153
154 91
        return $layer;
155
    }
156
157
    /**
158
     * Compare two items to see if they have same dimensions.
159
     */
160 42
    private static function isSameDimensions(Item $itemA, Item $itemB): bool
161
    {
162 42
        if ($itemA === $itemB) {
163 29
            return true;
164
        }
165 21
        $itemADimensions = [$itemA->getWidth(), $itemA->getLength(), $itemA->getDepth()];
166 21
        $itemBDimensions = [$itemB->getWidth(), $itemB->getLength(), $itemB->getDepth()];
167 21
        sort($itemADimensions);
168 21
        sort($itemBDimensions);
169
170 21
        return $itemADimensions === $itemBDimensions;
171
    }
172
}
173