static public PartList Pack(PartList parts, BoardList boards, double sawkerf = 4, double boardMarginLength = 0, double boardMarginWidth = 0, double partPaddingLength = 0, double partPaddingWidth = 0) { // order the parts by Area, Ascending var orderredParts = parts.OrderredByArea(); var oderredBoards = boards.OrderredByArea(); // add padding to parts orderredParts.InflateAll(partPaddingWidth, partPaddingLength); // init the bag for the solution PartList completeSolution = new PartList(); // repeat until all parts are placed, or boards used up int iteration = 0; while (orderredParts.Count > 0 && oderredBoards.Count > 0) { // for this iteration, prepare to hold the best board's packing solution PartList bestsolution = null; double bestsolutioncoverage = 0; // we will pack each board in its own thread, so we need to track the threads List <Task> threads = new List <Task>(); // loop through the available board sections for (BoardNode iBoard = oderredBoards.Head; iBoard != null; iBoard = iBoard.Next) { threads.Add( Task.Factory.StartNew((o) => { Thread.CurrentThread.Priority = ThreadPriority.Highest; // for every board BoardNode tiBoard = new BoardNode((BoardNode)o); // subtract the margin from the board tiBoard.Inflate(-boardMarginWidth, -boardMarginLength); // init a packer object Packer iPacker = new Packer() { boardArea = tiBoard.Area, sawkerf = sawkerf }; // pack the board recursively, starting at the first part and an empty solution iPacker.Pack_recursive(new PartList(orderredParts), new BoardList(tiBoard), new PartList(), 0); //Trace.WriteLine($"......in iteration {iteration+1}: Board {iPacker.currentSolution.Head.Container} packed to {iPacker.currentSolutionArea/iPacker.boardArea:0 %} :\r\n{iPacker.currentSolution.ToString()}"); // replace the best solution if this one is better lock (lck) if (iPacker.currentSolutionArea / iPacker.boardArea > bestsolutioncoverage) { bestsolutioncoverage = iPacker.currentSolutionArea / iPacker.boardArea; bestsolution = iPacker.currentSolution; } }, iBoard)); } Task.WaitAll(threads.ToArray()); // if no board could be packed, stop if (bestsolutioncoverage == 0) { Trace.WriteLine("STOPPING: Non of the parts left to place would fit any of the available boards..."); break; } boards[bestsolution.Head.Container].Solution = new PartList(bestsolution); // report the best packking for this iteration Trace.WriteLine($"Best solution for iteration {++iteration}: Board {bestsolution.Head.Container} packed to {bestsolutioncoverage:0 %} :\r\n{bestsolution.ToString()}"); // remove best packed board from the list of available boards oderredBoards.Remove(bestsolution.Head.Container); // remove the parts packed from the list of required parts for (PartNode iPart = bestsolution.Head; iPart != null; iPart = iPart.Next) { orderredParts.Remove(iPart.ID); } // add this partial solution to the complete solution... completeSolution.Append(bestsolution); } // return the solution return(completeSolution); }
private void Pack_recursive(PartList parts, BoardList boards, PartList TemporarySolution, double tempSolutionArea) { // loop through remaining parts for (PartNode iPart = parts.Tail; iPart != null; iPart = iPart.Prev) { #region // check if the part is a viable candidate ... // if this part has bigger area than the largest board segment available, go to next part if (iPart.Area > boards.Tail.Area) { continue; } // if the previous part was the same size, pass this one - we already completed this iteration if (iPart != parts.Tail && iPart.Length == iPart.Next.Length && iPart.Width == iPart.Next.Width) { continue; } #endregion #region // Find first board that will fit the part ... // find first board that will accomodate the part BoardNode iBoard = boards.Head; // if even the last (bigest) board had a smaller area than the part, non of the rest will fit while (iPart.Area > iBoard.Area) { iBoard = iBoard.Next; } while (iBoard != null && (iPart.Length > iBoard.Length || iPart.Width > iBoard.Width)) { iBoard = iBoard.Next; } if (iBoard == null) { continue; // if this part cannot fit any board, continue to next part } #endregion #region // place the part ... double newPackedPartsArea = tempSolutionArea + iPart.Area; //append the part to the list of packed parts TemporarySolution.Append(new PartNode(iPart) { Container = iBoard.ID, dWidth = iBoard.dWidth, dLength = iBoard.dLength }); #endregion #region // store best solution ... //if this is a better solution than the current best one ... replace the current best one if (newPackedPartsArea > currentSolutionArea) { currentSolutionArea = newPackedPartsArea; currentSolution = new PartList(TemporarySolution); } #endregion // if there are no more parts, break out of the loop if (parts.Count == 1) { break; } #region // Break association and adjust associate if a board is used that is associated with another to prevent overlapping placements ... BoardNode iAssocBoard = iBoard.AssociatedBoard; double oAssocLength = 0, oAssocWidth = 0; // if the board section used has a buddy from a previous placement, adjust the buddy and break the association if (iAssocBoard != null) { oAssocLength = iAssocBoard.Length; oAssocWidth = iAssocBoard.Width;; //we have to adjust the buddy, so as not to place another part on the overlapping area if (iBoard.dWidth < iAssocBoard.dWidth) { //if this is Rem1 //if the part is wider than the left portion of Rem1 if (iBoard.dWidth + iPart.Width + sawkerf > iAssocBoard.dWidth) { iAssocBoard.Length -= (iBoard.Length + sawkerf); } else { iBoard.Width -= (iAssocBoard.Width + sawkerf); } } else { //if this is Rem2 if (iBoard.dLength + iPart.Length + sawkerf > iAssocBoard.dLength) { iAssocBoard.Width -= (iBoard.Width + sawkerf); } else { iBoard.Length -= (iAssocBoard.Length + sawkerf); } } //then break the pair iAssocBoard.AssociatedBoard = null; iBoard.AssociatedBoard = null; } #endregion #region // remove the current part from the parts list ... parts.Remove(iPart); #endregion #region // replace the used board with 2 overlapping remainder pieces after subtracting the part ... // divide the board into two overlapping remainder sections boards.Remove(iBoard); BoardNode boardSection1 = null; double l = iBoard.Length - iPart.Length - sawkerf; if (l * iBoard.Width >= parts.Head.Area) { boardSection1 = new BoardNode(iBoard.ID, l, iBoard.Width, iBoard.dLength + iPart.Length + sawkerf, iBoard.dWidth); boards.InsertItemSortedbyAreaAsc(boardSection1); } BoardNode boardSection2 = null; double w = iBoard.Width - iPart.Width - sawkerf; if (w * iBoard.Length >= parts.Head.Area) { boardSection2 = new BoardNode(iBoard.ID, iBoard.Length, w, iBoard.dLength, iBoard.dWidth + iPart.Width + sawkerf); boards.InsertItemSortedbyAreaAsc(boardSection2); boardSection2.AssociatedBoard = boardSection1; if (boardSection1 != null) { boardSection1.AssociatedBoard = boardSection2; } } #endregion if (boards.Count > 0) { #region // pack the remaining parts on the remaining boards ... // pack the remaining parts on the remaining boards Pack_recursive(parts, boards, TemporarySolution, newPackedPartsArea); #endregion } #region // undo the placement so we can iterate to the next part and test with it ... // place the current part back in its exact place ... parts.Return(iPart); // remove the remainder board sections we added... if (boardSection1 != null) { boards.Remove(boardSection1); } if (boardSection2 != null) { boards.Remove(boardSection2); } // restore associations, and the original associated board's size if (iAssocBoard != null) { iBoard.AssociatedBoard = iAssocBoard; iAssocBoard.AssociatedBoard = iBoard; iAssocBoard.Length = oAssocLength; iAssocBoard.Width = oAssocWidth; } // place the board back if (iBoard.Prev == null) { boards.Head = iBoard; } else { iBoard.Prev.Next = iBoard; } if (iBoard.Next == null) { boards.Tail = iBoard; } else { iBoard.Next.Prev = iBoard; } boards.Count++; // remove the part from the temporary solution TemporarySolution.Tail = TemporarySolution.Tail.Prev; if (TemporarySolution.Tail != null) { TemporarySolution.Tail.Next = null; } else { TemporarySolution.Head = null; } TemporarySolution.Count--; #endregion } }