LayerPacker::setBoxIsRotated()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Box packing (3D bin packing, knapsack problem).
5
 *
6
 * @author Doug Wright
7
 */
8
declare(strict_types=1);
9
10
namespace DVDoug\BoxPacker;
11
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\NullLogger;
15
16
use function array_merge;
17
use function iterator_to_array;
18
use function max;
19
use function sort;
20
21
/**
22
 * Layer packer.
23
 * @internal
24
 */
25
class LayerPacker implements LoggerAwareInterface
26
{
27
    private LoggerInterface $logger;
28
29
    private bool $singlePassMode = false;
30
31
    private readonly OrientatedItemFactory $orientatedItemFactory;
32
33
    private bool $beStrictAboutItemOrdering = false;
34
35
    private bool $isBoxRotated = false;
36 101
37
    public function __construct(private readonly Box $box)
38 101
    {
39
        $this->logger = new NullLogger();
40 101
41 101
        $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...
42
        $this->orientatedItemFactory->setLogger($this->logger);
43
    }
44
45
    /**
46
     * Sets a logger.
47 101
     */
48
    public function setLogger(LoggerInterface $logger): void
49 101
    {
50 101
        $this->logger = $logger;
51
        $this->orientatedItemFactory->setLogger($logger);
52
    }
53 19
54
    public function setSinglePassMode(bool $singlePassMode): void
55 19
    {
56 19
        $this->singlePassMode = $singlePassMode;
57
        $this->orientatedItemFactory->setSinglePassMode($singlePassMode);
58
    }
59 101
60
    public function setBoxIsRotated(bool $boxIsRotated): void
61 101
    {
62 101
        $this->isBoxRotated = $boxIsRotated;
63
        $this->orientatedItemFactory->setBoxIsRotated($boxIsRotated);
64
    }
65 42
66
    public function beStrictAboutItemOrdering(bool $beStrict): void
67 42
    {
68
        $this->beStrictAboutItemOrdering = $beStrict;
69
    }
70
71
    /**
72
     * Pack items into an individual vertical layer.
73 101
     */
74
    public function packLayer(ItemList &$items, PackedItemList $packedItemList, int $startX, int $startY, int $startZ, int $widthForLayer, int $lengthForLayer, int $depthForLayer, int $guidelineLayerDepth, bool $considerStability, ?OrientatedItem $firstItem): PackedLayer
75 101
    {
76 101
        $layer = new PackedLayer();
77 101
        $x = $startX;
78 101
        $y = $startY;
79 101
        $z = $startZ;
80 101
        $rowLength = 0;
81 101
        $prevItem = null;
82 101
        $skippedItems = [];
83
        $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight();
84 101
85 101
        while ($items->count() > 0) {
86
            $itemToPack = $items->extract();
87
88 101
            // skip items that will never fit e.g. too heavy
89 18
            if ($itemToPack->getWeight() > $remainingWeightAllowed) {
90
                continue;
91
            }
92 99
93 92
            if ($firstItem instanceof OrientatedItem && $firstItem->item === $itemToPack) {
94 92
                $orientatedItem = $firstItem;
95
                $firstItem = null;
96 94
            } else {
97
                $orientatedItem = $this->orientatedItemFactory->getBestOrientation($itemToPack, $prevItem, $items, $widthForLayer - $x, $lengthForLayer - $y, $depthForLayer, $rowLength, $x, $y, $z, $packedItemList, $considerStability);
98
            }
99 99
100 98
            if ($orientatedItem instanceof OrientatedItem) {
101 98
                $packedItem = PackedItem::fromOrientatedItem($orientatedItem, $x, $y, $z);
102 98
                $layer->insert($packedItem);
103
                $packedItemList->insert($packedItem);
104 98
105 98
                $rowLength = max($rowLength, $packedItem->length);
106
                $prevItem = $orientatedItem;
107
108
                // Figure out if we can stack items on top of this rather than side by side
109 98
                // e.g. when we've packed a tall item, and have just put a shorter one next to it.
110 98
                $stackableDepth = ($guidelineLayerDepth ?: $layer->getDepth()) - $packedItem->depth;
111 33
                if ($stackableDepth > 0) {
112 33
                    $stackedLayer = $this->packLayer($items, $packedItemList, $x, $y, $z + $packedItem->depth, $x + $packedItem->width, $y + $packedItem->length, $stackableDepth, $stackableDepth, $considerStability, null);
113
                    $layer->merge($stackedLayer);
114
                }
115 98
116 98
                $x += $packedItem->width;
117
                $remainingWeightAllowed = $this->box->getMaxWeight() - $this->box->getEmptyWeight() - $packedItemList->getWeight(); // remember may have packed additional items
118
119 98
                // might be space available lengthwise across the width of this item, up to the current layer length
120
                $layer->merge($this->packLayer($items, $packedItemList, $x - $packedItem->width, $y + $packedItem->length, $z, $x, $y + $rowLength, $depthForLayer, $layer->getDepth(), $considerStability, null));
121 98
122 24
                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...
123 24
                    $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
124
                    $skippedItems = [];
125
                }
126 98
127
                continue;
128
            }
129 94
130 81
            if (!$this->beStrictAboutItemOrdering && $items->count() > 0) { // skip for now, move on to the next item
131 81
                $this->logger->debug("doesn't fit, skipping for now");
132
                $skippedItems[] = $itemToPack;
133 81
                // 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
134 39
                while ($items->count() > 1 && self::isSameDimensions($itemToPack, $items->top())) {
135
                    $skippedItems[] = $items->extract();
136 81
                }
137
                continue;
138
            }
139 94
140 89
            if ($x > $startX) {
141 89
                $this->logger->debug('No more fit in width wise, resetting for new row');
142 89
                $y += $rowLength;
143 89
                $x = $startX;
144 89
                $rowLength = 0;
145 89
                $skippedItems[] = $itemToPack;
146 89
                $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
147 89
                $skippedItems = [];
148 89
                $prevItem = null;
149
                continue;
150
            }
151 94
152 94
            $this->logger->debug('no items fit, so starting next vertical layer');
153
            $skippedItems[] = $itemToPack;
154 94
155
            $items = ItemList::fromArray(array_merge($skippedItems, iterator_to_array($items)), true);
156 94
157
            return $layer;
158
        }
159 98
160
        return $layer;
161
    }
162
163
    /**
164
     * Compare two items to see if they have same dimensions.
165 55
     */
166
    private static function isSameDimensions(Item $itemA, Item $itemB): bool
167 55
    {
168 34
        if ($itemA === $itemB) {
169
            return true;
170 33
        }
171 33
        $itemADimensions = [$itemA->getWidth(), $itemA->getLength(), $itemA->getDepth()];
172 33
        $itemBDimensions = [$itemB->getWidth(), $itemB->getLength(), $itemB->getDepth()];
173 33
        sort($itemADimensions);
174
        sort($itemBDimensions);
175 33
176
        return $itemADimensions === $itemBDimensions;
177
    }
178
}
179