Passed
Pull Request — master (#549)
by
unknown
15:13 queued 13:32
created

OrientatedItemFactory::getPossibleOrientations()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 40
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 21.0181

Importance

Changes 9
Bugs 0 Features 0
Metric Value
cc 9
eloc 16
c 9
b 0
f 0
nc 6
nop 9
dl 0
loc 40
ccs 8
cts 17
cp 0.4706
crap 21.0181
rs 8.0555

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 Psr\Log\LoggerAwareInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
15
use function array_filter;
16
use function count;
17
use function usort;
18
19
/**
20
 * Figure out orientations for an item and a given set of dimensions.
21
 * @internal
22
 */
23
class OrientatedItemFactory implements LoggerAwareInterface
24
{
25
    protected LoggerInterface $logger;
26
27
    protected bool $singlePassMode = false;
28
29
    protected bool $boxIsRotated = false;
30
31
    /**
32
     * @var array<string, bool>
33
     */
34
    protected static array $emptyBoxStableItemOrientationCache = [];
35
36 84
    public function __construct(protected Box $box)
37
    {
38 84
        $this->logger = new NullLogger();
39
    }
40
41 84
    public function setLogger(LoggerInterface $logger): void
42
    {
43 84
        $this->logger = $logger;
44
    }
45
46 4
    public function setSinglePassMode(bool $singlePassMode): void
47
    {
48 4
        $this->singlePassMode = $singlePassMode;
49
    }
50
51 84
    public function setBoxIsRotated(bool $boxIsRotated): void
52
    {
53 84
        $this->boxIsRotated = $boxIsRotated;
54
    }
55
56
    /**
57
     * Get the best orientation for an item.
58
     */
59 84
    public function getBestOrientation(
60
        Item $item,
61
        ?OrientatedItem $prevItem,
62
        ItemList $nextItems,
63
        int $widthLeft,
64
        int $lengthLeft,
65
        int $depthLeft,
66
        int $rowLength,
67
        int $x,
68
        int $y,
69
        int $z,
70
        PackedItemList $prevPackedItemList,
71
        bool $considerStability
72
    ): ?OrientatedItem {
73 84
        $this->logger->debug(
74 84
            "evaluating item {$item->getDescription()} for fit",
75 84
            [
76 84
                'item' => $item,
77 84
                'space' => [
78 84
                    'widthLeft' => $widthLeft,
79 84
                    'lengthLeft' => $lengthLeft,
80 84
                    'depthLeft' => $depthLeft,
81 84
                ],
82 84
                'position' => [
83 84
                    'x' => $x,
84 84
                    'y' => $y,
85 84
                    'z' => $z,
86 84
                ],
87 84
            ]
88 84
        );
89
90 84
        $possibleOrientations = $this->getPossibleOrientations($item, $prevItem, $widthLeft, $lengthLeft, $depthLeft, $x, $y, $z, $prevPackedItemList);
91 84
        $usableOrientations = $considerStability ? $this->getUsableOrientations($item, $possibleOrientations) : $possibleOrientations;
92
93 84
        if (empty($usableOrientations)) {
94 80
            return null;
95
        }
96
97 82
        $sorter = new OrientatedItemSorter($this, $this->singlePassMode, $widthLeft, $lengthLeft, $depthLeft, $nextItems, $rowLength, $x, $y, $z, $prevPackedItemList, $this->logger);
98 82
        usort($usableOrientations, $sorter);
99
100 82
        $this->logger->debug('Selected best fit orientation', ['orientation' => $usableOrientations[0]]);
101
102 82
        return $usableOrientations[0];
103
    }
104
105
    /**
106
     * Find all possible orientations for an item.
107
     *
108
     * @return OrientatedItem[]
109
     */
110 84
    public function getPossibleOrientations(
111
        Item $item,
112
        ?OrientatedItem $prevItem,
113
        int $widthLeft,
114
        int $lengthLeft,
115
        int $depthLeft,
116
        int $x,
117
        int $y,
118
        int $z,
119
        PackedItemList $prevPackedItemList
120
    ): array {
121 84
        $permutations = $this->generatePermutations($item, $prevItem);
122
123
        // remove any that simply don't fit
124 84
        $orientations = [];
125 84
        foreach ($permutations as $dimensions) {
126 84
            if ($dimensions[0] <= $widthLeft && $dimensions[1] <= $lengthLeft && $dimensions[2] <= $depthLeft) {
127 82
                $orientations[] = new OrientatedItem($item, $dimensions[0], $dimensions[1], $dimensions[2]);
128
            }
129
        }
130
131 84
        if ($item instanceof ConstrainedPlacementItem && !$this->box instanceof WorkingVolume) {
132
            $orientations = array_filter($orientations, function (OrientatedItem $i) use ($x, $y, $z, $prevPackedItemList): bool {
133
                /** @var ConstrainedPlacementItem $constrainedItem */
134
                $constrainedItem = $i->item;
135
136
                if ($this->boxIsRotated) {
137
                    $rotatedPrevPackedItemList = new PackedItemList();
138
                    foreach ($prevPackedItemList as $prevPackedItem) {
139
                        $rotatedPrevPackedItemList->insert(new PackedItem($prevPackedItem->item, $prevPackedItem->y, $prevPackedItem->x, $prevPackedItem->z, $prevPackedItem->length, $prevPackedItem->width, $prevPackedItem->depth));
140
                    }
141
142
                    return $constrainedItem->canBePacked(new PackedBox($this->box, $rotatedPrevPackedItemList), $y, $x, $z, $i->length, $i->width, $i->depth);
143
                } else {
144
                    return $constrainedItem->canBePacked(new PackedBox($this->box, $prevPackedItemList), $x, $y, $z, $i->width, $i->length, $i->depth);
145
                }
146
            });
147
        }
148
149 84
        return $orientations;
150
    }
151
152
    /**
153
     * @param  OrientatedItem[] $possibleOrientations
154
     * @return OrientatedItem[]
155
     */
156 84
    protected function getUsableOrientations(
157
        Item $item,
158
        array $possibleOrientations
159
    ): array {
160 84
        $stableOrientations = $unstableOrientations = [];
161
162
        // Divide possible orientations into stable (low centre of gravity) and unstable (high centre of gravity)
163 84
        foreach ($possibleOrientations as $orientation) {
164 82
            if ($orientation->isStable() || $this->box->getInnerDepth() === $orientation->depth) {
165 82
                $stableOrientations[] = $orientation;
166
            } else {
167
                $unstableOrientations[] = $orientation;
168
            }
169
        }
170
171
        /*
172
         * We prefer to use stable orientations only, but allow unstable ones if
173
         * the item doesn't fit in the box any other way
174
         */
175 84
        if (count($stableOrientations) > 0) {
176 82
            return $stableOrientations;
177
        }
178
179 80
        if ((count($unstableOrientations) > 0) && !$this->hasStableOrientationsInEmptyBox($item)) {
180
            return $unstableOrientations;
181
        }
182
183 80
        return [];
184
    }
185
186
    /**
187
     * Return the orientations for this item if it were to be placed into the box with nothing else.
188
     */
189
    protected function hasStableOrientationsInEmptyBox(Item $item): bool
190
    {
191
        $cacheKey = $item->getWidth() .
192
            '|' .
193
            $item->getLength() .
194
            '|' .
195
            $item->getDepth() .
196
            '|' .
197
            $item->getAllowedRotation()->name .
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on DVDoug\BoxPacker\Rotation.
Loading history...
198
            '|' .
199
            $this->box->getInnerWidth() .
200
            '|' .
201
            $this->box->getInnerLength() .
202
            '|' .
203
            $this->box->getInnerDepth();
204
205
        if (isset(static::$emptyBoxStableItemOrientationCache[$cacheKey])) {
206
            return static::$emptyBoxStableItemOrientationCache[$cacheKey];
207
        }
208
209
        $orientations = $this->getPossibleOrientations(
210
            $item,
211
            null,
212
            $this->box->getInnerWidth(),
213
            $this->box->getInnerLength(),
214
            $this->box->getInnerDepth(),
215
            0,
216
            0,
217
            0,
218
            new PackedItemList()
219
        );
220
221
        $stableOrientations = array_filter(
222
            $orientations,
223
            static fn (OrientatedItem $orientation) => $orientation->isStable()
224
        );
225
        static::$emptyBoxStableItemOrientationCache[$cacheKey] = count($stableOrientations) > 0;
226
227
        return static::$emptyBoxStableItemOrientationCache[$cacheKey];
228
    }
229
230
    /**
231
     * @return array<array<int>>
232
     */
233 84
    private function generatePermutations(Item $item, ?OrientatedItem $prevItem): array
234
    {
235
        // Special case items that are the same as what we just packed - keep orientation
236 84
        if ($prevItem && $prevItem->isSameDimensions($item)) {
237 62
            return [[$prevItem->width, $prevItem->length, $prevItem->depth]];
238
        }
239
240 84
        $permutations = [];
241 84
        $w = $item->getWidth();
242 84
        $l = $item->getLength();
243 84
        $d = $item->getDepth();
244
245 84
        $permutations[$w . '|' . $l . '|' . $d] = [$w, $l, $d];
246
247 84
        if ($item->getAllowedRotation() !== Rotation::Never) { // simple 2D rotation
248 84
            $permutations[$l . '|' . $w . '|' . $d] = [$l, $w, $d];
249
        }
250
251 84
        if ($item->getAllowedRotation() === Rotation::BestFit) { // add 3D rotation if we're allowed
252 52
            $permutations[$w . '|' . $d . '|' . $l] = [$w, $d, $l];
253 52
            $permutations[$l . '|' . $d . '|' . $w] = [$l, $d, $w];
254 52
            $permutations[$d . '|' . $w . '|' . $l] = [$d, $w, $l];
255 52
            $permutations[$d . '|' . $l . '|' . $w] = [$d, $l, $w];
256
        }
257
258 84
        return $permutations;
259
    }
260
}
261