/// <summary> /// Pack the list of boxes into a pallet. In other words put small boxes into a big one. /// Function packs pallet in layers and tries all pallet rotations and all possible /// starting layer sizes. /// Returned pallet could be null if the list of boxes was empty or packed pallet that may /// contains all or only some boxes. /// Packing is done in the loop each time incrementing IterationCount field. /// Packing stops when either all boxes fit in the pallet, packing times out or the number of packing iterations /// is above the specified limit. /// </summary> /// <param name="boxList">The list of boxes to pack.</param> /// <param name="palletSize">Pallet dimensions.</param> /// <returns>Null if box list is empty or packed pallet</returns> public PackedPallet Pack(List <Box> boxList, Point3D palletSize) { if (boxList == null || boxList.Count == 0) { return(null); } IterationCount = 0; // Start a timer if needed for timeout if (TimeoutMilliseconds.HasValue) { stopwatch = Stopwatch.StartNew(); } else { stopwatch = null; } PackedPallet bestPackedPallet = null; // Try packing pallet in all orientations foreach (var rotatedPallet in palletSize.AllRotations) { layers = CreateLayers(boxList, rotatedPallet); // Try packing pallet starting with different layer foreach (var layer in layers) { if (ShouldCancelPacking()) { break; } ++IterationCount; var packedPallet = PackPallet(layer, boxList, rotatedPallet); if (ShouldCancelPacking()) { break; } if (bestPackedPallet == null || packedPallet.PackedVolume > bestPackedPallet.PackedVolume) { bestPackedPallet = packedPallet; } if (bestPackedPallet.AllBoxesPacked) { return(bestPackedPallet); } } // If pallet is a cube then rotating it makes no sense. // Just exit after the first packing attempt if (palletSize.IsCube) { break; } } return(bestPackedPallet); }
/// <summary> /// Pack boxes into a pallet starting with specified layer thickness. /// The minor "optimization" here is sub-layer packing that is performed /// if not enough boxes are available in the layer and layer has to grow to /// accomodate bigger available boxes /// </summary> /// <param name="startLayer">The layer to start with</param> /// <param name="boxList">All boxes to pack</param> /// <param name="palletDimensions">Dimensions of the pallet</param> /// <returns>Packed pallet object</returns> PackedPallet PackPallet(Layer startLayer, List <Box> boxList, Point3D palletDimensions) { var pallet = new PackedPallet() { PalletDimensions = palletDimensions, NotPackedBoxes = boxList.Select(box => box.Clone()).ToList(), PackedBoxes = new List <Box>() }; // Start building layers at Y=0 var layerBottomY = 0L; var layerThickness = startLayer.LayerThickness; do { subLayerThickness = 0; var oldLayerThickness = layerThickness; layerThickness = PackLayer(pallet, layerBottomY, layerThickness, palletDimensions.Y - layerBottomY, palletDimensions.Z); if (pallet.AllBoxesPacked) { break; } if (subLayerThickness != 0 && !ShouldCancelPacking()) { // Pack the special case where layer starts at oldLayerThickness but later // becomes thicker to accomodate taller boxes like this: // [XX] // [X] [X] [X] [XX] // This call would pack the part of the layer marked by * // ************[XX] // [X] [X] [X] [XX] PackLayer(pallet, layerBottomY + oldLayerThickness, subLayerThickness, layerThickness - oldLayerThickness, sublayerZlimit); if (pallet.AllBoxesPacked) { break; } } // This layer was packed. Raise the level by packed layer thickness layerBottomY += layerThickness; // Find the next layer to pack layerThickness = FindLayer(pallet.NotPackedBoxes, palletDimensions.SubtractY(layerBottomY)); if (layerThickness == 0) { break; } } while (!ShouldCancelPacking()); return(pallet); }
/// <summary> /// Pack one layer. If during packing some boxes become taller than layerThickness then /// increase the layer thickness to accomodate them. /// </summary> /// <param name="pallet">Pallet that is being packed</param> /// <param name="layerBottomY">The bottom Y coordinate of this layer</param> /// <param name="layerThickness">The thickness (Y) of this layer</param> /// <param name="maxThickness">The max thickness possible for this layer in the pallet</param> /// <param name="layerZlimit">The Z limit for this layer. Usually the pallet size but in case of /// sub-layer it can be less</param> /// <returns></returns> long PackLayer(PackedPallet pallet, long layerBottomY, long layerThickness, long maxThickness, long layerZlimit) { if (layerThickness == 0) { return(layerThickness); } // Create 2d packing line of the layer. // It grows in a horizontal plane with X being width and Z height of this line. var packLine = new PackLine(pallet.PalletDimensions.X); while (!ShouldCancelPacking()) { var valley = packLine.FindValley(); // Calculate ideal space and maximum space var topZ = valley.NoSegmentsOnSides ? layerZlimit : (valley.NoLeftSegment ? valley.Right.Z : valley.Left.Z); var idealBox = new Point3D() { X = valley.Width, Y = layerThickness, Z = topZ - valley.Z }; var maximumBox = idealBox.WithYZ(maxThickness, layerZlimit - valley.Z); // Find the box that fits in the remaining space of the pallet var selected = FindBox(pallet.NotPackedBoxes, idealBox, maximumBox); if (selected == null || !selected.FitsInLayer) { if (selected != null && (subLayerThickness != 0 || valley.NoSegmentsOnSides)) { // We have a box but it is too thick for the current layer if (subLayerThickness == 0) { // sub-layer will be from 0 to valleyZ sublayerZlimit = valley.Z; } // sub-layer thickness subLayerThickness = subLayerThickness + selected.PackedDimensions.Y - layerThickness; // Now thicken the current layer since we have no more small boxes layerThickness = selected.PackedDimensions.Y; } else if (valley.NoSegmentsOnSides) { // No more boxes for this layer break; } else { valley.FillValley(); continue; } } // Record box location in the pallet and pack the box selected.Box.PackingData = new PackingData() { PackedDimensions = selected.PackedDimensions, PackedLocation = new Point3D() { X = valley.LeftX, Y = layerBottomY, Z = valley.Z } }; pallet.PackBox(selected.Box); // Add the box dimensions to the pack line packLine.AddZ(valley, selected.PackedDimensions); if (pallet.AllBoxesPacked) { break; } } return(layerThickness); }