Example #1
0
        /// <summary>
        /// Draw a board to an image and return the image as a base64 string
        /// </summary>
        /// <param name="board"></param>
        /// <returns></returns>
        private Base64Image DrawBoard_base64(StockItem board)
        {
            // constants used in drawing the image
            const double xMargin     = 0;
            const double yMargin     = 20;
            double       imageHeight = board.Width + 2 * yMargin;
            double       imageWidth  = board.Length + 2 * xMargin;

            // create bitmap
            System.Drawing.Bitmap   bitmap = new System.Drawing.Bitmap((int)imageWidth, (int)imageHeight);
            System.Drawing.Graphics g      = System.Drawing.Graphics.FromImage(bitmap);

            // fill the background
            g.FillRectangle(System.Drawing.Brushes.White, 0, 0, (int)imageWidth, (int)imageHeight);

            // draw the board
            g.FillRectangle(System.Drawing.Brushes.DarkRed, (float)(xMargin), (float)yMargin, (float)board.Length, (float)board.Width);

            // loop through all the parts and draw the ones on the current board
            for (int i = 0; i < board.PackedPartsCount; i++)
            {
                Placement iPlacement = board.PackedParts[i];

                // draw the part
                g.FillRectangle(System.Drawing.Brushes.Green,
                                (float)(xMargin + iPlacement.dLength),
                                (float)(yMargin + iPlacement.dWidth),
                                (float)iPlacement.Part.Length,
                                (float)iPlacement.Part.Width);

                // print the part text
                string text1 = $"{iPlacement.Part.Name} [{iPlacement.Part.Length} x {iPlacement.Part.Width}]";
                System.Drawing.Font  partFont = new System.Drawing.Font(new System.Drawing.FontFamily("Consolas"), 15);
                System.Drawing.SizeF textSize = g.MeasureString(text1, partFont);
                if (textSize.Width > iPlacement.Part.Length)
                {
                    text1 = iPlacement.Part.Name;
                }
                textSize = g.MeasureString(text1, partFont);
                g.DrawString(text1, partFont, System.Drawing.Brushes.White,
                             (int)(xMargin + iPlacement.dLength + iPlacement.Part.Length / 2.0 - textSize.Width / 2.0),
                             (int)(yMargin + iPlacement.dWidth + iPlacement.Part.Width / 2.0 - textSize.Height / 2.0));
            }

            // make sure the cache is empty
            g.Flush();

            // convert image to base64 image
            System.IO.MemoryStream ms = new MemoryStream();
            bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            byte[] byteImage = ms.ToArray();
            var    base64img = Convert.ToBase64String(byteImage);

            // return the class for the image
            return(new Base64Image()
            {
                image = base64img,
                Height = bitmap.Height,
                Width = bitmap.Width
            });
        }
Example #2
0
        /// <summary>
        ///  do the internal preperation to pack a set of parts onto a set of boards with a collection of options
        /// </summary>
        /// <param name="parts"></param>
        /// <param name="boards"></param>
        /// <param name="sawkerf"></param>
        /// <param name="boardMarginLength"></param>
        /// <param name="boardMarginWidth"></param>
        /// <param name="partLengthPadding"></param>
        /// <param name="partWidthPadding"></param>
        /// <returns></returns>
        public void Pack(Part[] parts, StockItem[] boards, double sawkerf = 3.2, double partLengthPadding = 0, double partWidthPadding = 0)
        {
            // order the parts and boards by Area, Ascending
            int partsCount = parts.Length;

            Part[] orderredParts = parts.OrderBy(t => t.Area).ToArray();
            int    boardsCount   = boards.Length;

            StockItem[] orderredBoards = boards.OrderBy(t => t.Area).ToArray();

            // add padding to all parts
            if (partLengthPadding > 0 || partWidthPadding > 0)
            {
                orderredParts.ToList().ForEach(t => t.Inflate(partWidthPadding, partLengthPadding));
            }

            // keep count of the parts and boards used
            int packedPartsCount  = 0;
            int packedBoardsCount = 0;

            // repeat until all parts are placed, or all boards packed
            int iteration = 0;

            while (packedPartsCount < partsCount && packedBoardsCount < boardsCount)
            {
                iteration++;
                Task[] threads = new Task[boardsCount];
                for (int i = 0; i < boardsCount; i++)
                {
                    threads[i] = Task.Factory.StartNew((o) =>
                    {
                        // reference board[i]
                        StockItem iBoard = orderredBoards[(int)o];
                        if (iBoard.isComplete)
                        {
                            return;
                        }
                        //if (iBoard.Name != "B") return;

                        // init a packer object
                        Packer_internal iPacker = new Packer_internal()
                        {
                            sawkerf                 = sawkerf,
                            Board                   = iBoard,
                            Parts                   = orderredParts,
                            PartsCount              = partsCount,
                            BoardSections           = new StockItem[2 * partsCount + 2],
                            BoardSectionsCount      = 1,
                            CurrentSolution         = new Part[partsCount],
                            CurrentSolutionDLengths = new double[partsCount],
                            CurrentSolutionDWidths  = new double[partsCount],
                            iteration               = iteration
                        };
                        iPacker.BoardSections[0] = new StockItem(iBoard.Name, iBoard.Length, iBoard.Width, iBoard.dLength, iBoard.dWidth);

                        // pack the board recursively, starting at the first part and an empty solution
                        iPacker.StartPacking(0);

                        StringBuilder sb = new StringBuilder();
                        sb.AppendLine($"board packed: {iBoard} ({iBoard.PackedPartsTotalArea / iBoard.Area * 100:0.0}%)");
                        for (int j = 0; j < iBoard.PackedPartsCount; j++)
                        {
                            sb.AppendLine($"   {iBoard.PackedParts[j]} @ ({iBoard.PackedPartdLengths[j]}, {iBoard.PackedPartdWidths[j]})");
                        }
                        Trace.WriteLine(sb.ToString());
                    }, i);
                }
                Task.WaitAll(threads);

                // Find the best packed board from this iteration
                IEnumerable <StockItem> incompleteBoards         = orderredBoards.Where(q => !q.isComplete);
                StockItem[]             PackedBestCoverredBoards = incompleteBoards.Where(q => q.PackedParts != null).OrderByDescending(t => t.PackedPartsTotalArea / t.Area).ToArray();

                Trace.WriteLine($"---------------------------------------------");
                Trace.WriteLine($"best board(s) for iteration:");
                // If no board could be packed, exit
                if (PackedBestCoverredBoards.Length == 0)
                {
                    break;
                }

                // loop through boards
                for (int iPacked = 0; iPacked < PackedBestCoverredBoards.Length; iPacked++)
                {
                    StockItem iBestCoverredBoard = PackedBestCoverredBoards[iPacked];
                    // if non of te parts packed on the board have been packed on a previous board
                    if (!iBestCoverredBoard.PackedParts.Any(t => t?.isPacked ?? false))
                    {
                        // use this packing
                        Trace.WriteLine($"{iBestCoverredBoard} ({iBestCoverredBoard.PackedPartsTotalArea / iBestCoverredBoard.Area * 100:0.0}%)");
                        for (int j = 0; j < iBestCoverredBoard.PackedPartsCount; j++)
                        {
                            Trace.WriteLine($"   {iBestCoverredBoard.PackedParts[j]} @ ({iBestCoverredBoard.PackedPartdLengths[j]}, {iBestCoverredBoard.PackedPartdWidths[j]})");
                        }

                        // set the complete flag for the board with the best coverage
                        iBestCoverredBoard.isComplete = true;
                        packedBoardsCount++;

                        if (iBestCoverredBoard.PackedParts != null)
                        {
                            //Compact the packed parts array of the board
                            Array.Resize <Part>(ref iBestCoverredBoard.PackedParts, iBestCoverredBoard.PackedPartsCount);

                            // set the packed flag for the packed parts
                            iBestCoverredBoard.PackedParts.ToList().ForEach(t => t.isPacked = true);
                        }
                        packedPartsCount += iBestCoverredBoard.PackedPartsCount;
                    }
                    else
                    {
                        // Clear the inferior packings
                        iBestCoverredBoard.PackedParts          = null;
                        iBestCoverredBoard.PackedPartdLengths   = null;
                        iBestCoverredBoard.PackedPartdWidths    = null;
                        iBestCoverredBoard.PackedPartsCount     = 0;
                        iBestCoverredBoard.PackedPartsTotalArea = 0;
                    }
                }
            }

            // remove padding to all parts
            if (partLengthPadding > 0 || partWidthPadding > 0)
            {
                orderredParts.ToList().ForEach(t => t.Inflate(-partWidthPadding, -partLengthPadding));
            }
        }
Example #3
0
        /// <summary>
        /// Pack the parts on the boards
        /// </summary>
        /// <param name="parts"></param>
        /// <param name="boards"></param>
        /// <param name="sawkerf"></param>
        /// <param name="partLengthPadding"></param>
        /// <param name="partWidthPadding"></param>
        public void Pack(Part[] parts, StockItem[] boards, double sawkerf = 3.2, double partLengthPadding = 0, double partWidthPadding = 0)
        {
            //clear current packing
            parts.ToList().ForEach(t => t.IsPacked = false);
            boards.ToList().ForEach(t =>
            {
                t.IsComplete           = false;
                t.PackedParts          = null;
                t.PackedPartsCount     = 0;
                t.PackedPartsTotalArea = 0;
            });


            // order the parts by Area, Ascending
            int boardsCount       = boards.Length;
            int packedPartsCount  = 0;
            int packedBoardsCount = 0;
            int partsCount        = parts.Length;

            Part[] orderredParts = parts.OrderBy(t => t.Area).ToArray();

            // loop until no parts were packed or all parts packed or all boards used
            while (packedPartsCount < partsCount && packedBoardsCount < boardsCount)
            {
                // launch a thread per board to pack it
                List <Task> threads = new List <Task>();
                for (int i = 0; i < boardsCount; i++)
                {
                    if (!boards[i].IsComplete)
                    {
                        threads.Add(Task.Factory.StartNew((o) =>
                        {
                            // reference board[i]
                            StockItem iBoard = boards[(int)o];

                            // clear the solution for the board
                            iBoard.PackedParts          = new Placement[partsCount];
                            iBoard.PackedPartsCount     = 0;
                            iBoard.PackedPartsTotalArea = 0;
                            iBoard.IsComplete           = false;

                            // create the two original points for the board
                            PointD[] points = new PointD[partsCount * 2 + 2];
                            points[0]       = new PointD(0, 0);
                            points[1]       = new PointD(iBoard.Width + sawkerf, iBoard.Length + sawkerf)
                            {
                                disabled = true
                            };
                            int pointCount  = 2;
                            int iPointIndex = -1;

                            // continuously iterate throught the points until we reach the end of the list of points (restart from first point if a part is placed)
                            while (++iPointIndex < pointCount)
                            {
                                PointD iPoint = points[iPointIndex];
                                if (iPoint.disabled)
                                {
                                    continue; // ignore disabled points
                                }
                                #region       // determine hight and width of available area at the point...
                                IEnumerable <PointD> limitinpoints = points.Where(q => q?.dLength > iPoint.dLength && q?.dWidth >= iPoint.dWidth);


                                PointD limitingPoint = points[pointCount - 1];
                                if (limitinpoints.Count() > 0)
                                {
                                    limitingPoint = limitinpoints.OrderBy(so => so.dWidth).First();
                                }
                                double maxx = limitingPoint.dWidth;
                                double maxy = iBoard.Length;

                                double maxWidth  = maxx - sawkerf - iPoint.dWidth;
                                double maxLength = maxy - iPoint.dLength;   // - sawkerf;
                                if (maxWidth <= 0 || maxLength <= 0)
                                {
                                    iPoint.disabled = true;
                                    continue;
                                }
                                #endregion

                                #region    // search for a part that will fit on the area on the board ...
                                // test each part for fit on the area for the point
                                bool partplaced = false;
                                for (int iPartIndex = partsCount - 1; iPartIndex >= 0; iPartIndex--)
                                {
                                    Part iPart = orderredParts[iPartIndex];
                                    // ignore parts already packed
                                    if (iPart.IsPacked || iBoard.PackedParts.Any(t => t?.Part == iPart))
                                    {
                                        continue;
                                    }

                                    // if the part will fit
                                    if (iPart.Length + partLengthPadding <= maxLength && iPart.Width + partWidthPadding <= maxWidth)
                                    {
                                        #region    // place the part onto the board at the point ...
                                        iBoard.PackedParts[iBoard.PackedPartsCount] = new Placement()
                                        {
                                            Part    = iPart,
                                            dLength = iPoint.dLength,
                                            dWidth  = iPoint.dWidth
                                        };
                                        iBoard.PackedPartsTotalArea += iPart.Area;
                                        iBoard.PackedPartsCount++;
                                        #endregion

                                        #region    // create new points for the top-right and bottom left corners of the part ...
                                        PointD newBL = new PointD(iPoint.dWidth, iPoint.dLength + iPart.Length + sawkerf + partLengthPadding);
                                        PointD newTR = new PointD(iPoint.dWidth + iPart.Width + sawkerf + partWidthPadding, iPoint.dLength);
                                        #endregion

                                        #region    // disable the existing points coverred by the new part
                                        for (int j = 0; j < pointCount; j++)
                                        {
                                            PointD jPoint = points[j];
                                            if (jPoint.dWidth >= iPoint.dWidth && jPoint.dWidth <= iPoint.dWidth + iPart.Width && jPoint.dLength >= iPoint.dLength)
                                            {
                                                jPoint.disabled = true;
                                            }
                                        }
                                        #endregion

                                        #region    // insert new points into orderred array ...
                                        int di = pointCount - 1;
                                        if (newBL != null)
                                        {
                                            while (points[di].dLength > newBL.dLength || points[di].dLength == newBL.dLength && points[di].dWidth > newBL.dWidth)
                                            {
                                                points[1 + di] = points[di--];
                                            }
                                            points[1 + di] = newBL;
                                            pointCount++;
                                        }

                                        if (newTR != null)
                                        {
                                            di = pointCount - 1;
                                            while (points[di].dLength > newTR.dLength || points[di].dLength == newTR.dLength && points[di].dWidth > newTR.dWidth)
                                            {
                                                points[1 + di] = points[di--];
                                            }
                                            points[1 + di] = newTR;
                                            pointCount++;
                                        }
                                        #endregion

                                        partplaced = true;
                                        break;
                                    }
                                }
                                #endregion

                                // if no parts fit this point's area
                                if (!partplaced)
                                {
                                    // disable this point
                                    iPoint.disabled = true;

                                    // if this part's area was not limited by the board's edge
                                    if (limitingPoint.dLength < iBoard.Length + sawkerf)
                                    {
                                        // create a new point at the same dLength value as the one that limited the width - maybe the extra width will allow a part to be placed there
                                        PointD newPoint = new PointD(iPoint.dWidth, limitingPoint.dLength);
                                        // insert the new point into the orderred array
                                        int di = pointCount - 1;
                                        while (points[di].dLength > newPoint.dLength || points[di].dLength == newPoint.dLength && points[di].dWidth > newPoint.dWidth)
                                        {
                                            points[1 + di] = points[di--];
                                        }

                                        points[1 + di] = newPoint;
                                        pointCount++;
                                    }
                                }
                                else
                                {
                                    // we placed a part - traverse all the points again
                                    iPointIndex = -1;
                                }
                            }
                        }, i));
                    }
                }

                // wait until all boards are packed
                Task.WaitAll(threads.ToArray());

                // Find the best packed board from this iteration
                StockItem[] incompleteBoards   = boards.Where(q => !q.IsComplete).ToArray();
                StockItem[] newlypackedBoards  = incompleteBoards.Where(q => q.PackedPartsCount > 0).ToArray();
                StockItem   iBestCoverredBoard = newlypackedBoards.OrderByDescending(t => t.PackedPartsTotalArea / t.Area)
                                                 .FirstOrDefault();

                if (iBestCoverredBoard == null)
                {
                    return;
                }

                // set the complete flag for the board with the best coverage
                iBestCoverredBoard.IsComplete = true;
                packedBoardsCount++;

                //Compact the packed parts array of the board
                Array.Resize <Placement>(ref iBestCoverredBoard.PackedParts, iBestCoverredBoard.PackedPartsCount);

                // set the packed flag for the packed parts
                iBestCoverredBoard.PackedParts.ToList().ForEach(t => t.Part.IsPacked = true);
                packedPartsCount += iBestCoverredBoard.PackedPartsCount;


                // clear the packing for all the unsuccessfull boards
                for (int iPacked = 0; iPacked < boardsCount; iPacked++)
                {
                    StockItem iBoard = boards[iPacked];
                    // if non of te parts packed on the board have been packed on a previous board
                    if (!iBoard.IsComplete)
                    {
                        iBoard.PackedParts          = null;
                        iBoard.PackedPartsCount     = 0;
                        iBoard.PackedPartsTotalArea = 0;
                        iBoard.IsComplete           = false;
                    }
                }
            }
        }
Example #4
0
            public void StartPacking(int iStart)
            {
                double lastPartLength = -1;
                double lastPartWidth  = -1;

                // loop through the parts, from big to small
                for (int i = iStart; i < PartsCount; i++)
                {
                    Part iPart = Parts[i];

                    #region // check if the part is a viable candidate ...
                    // ignore parts already packed
                    if (iPart.isPacked)
                    {
                        continue;
                    }
                    // ignore parts already temporarily packed
                    if (CurrentSolution.Contains(iPart))
                    {
                        continue;
                    }
                    // ignore parts larger than the largest board section
                    if (iPart.Area > Board.Area)
                    {
                        break;
                    }
                    // short-circuit repeat parts
                    if (iPart.Length == lastPartLength && iPart.Width == lastPartWidth)
                    {
                        continue;
                    }



                    lastPartLength = iPart.Length;
                    lastPartWidth  = iPart.Width;
                    #endregion

                    #region // Find first board that will fit the part ...
                    // find first board that will accomodate the part
                    int j = 0;
                    //while (j < BoardSectionsCount && BoardSections[j].Area < iPart.Area) j++;
                    while (j < BoardSectionsCount && (BoardSections[j].isInUse || iPart.Length > BoardSections[j].Length || iPart.Width > BoardSections[j].Width))
                    {
                        j++;
                    }

                    // if no boards will accomodate the part, continue to next part
                    if (j >= BoardSectionsCount)
                    {
                        continue;
                    }
                    StockItem iBoardSection = BoardSections[j];
                    #endregion

                    #region // place the part in the current bin ...
                    CurrentSolutionDLengths[CurrentSolutionPartCount] = iBoardSection.dLength;
                    CurrentSolutionDWidths[CurrentSolutionPartCount]  = iBoardSection.dWidth;
                    CurrentSolution[CurrentSolutionPartCount++]       = iPart;
                    CurrentSolutionTotalArea += iPart.Area;
                    iBoardSection.isInUse     = true;
                    #endregion

                    #region // store best solution ...
                    //if this is a better solution than the current best one ... replace the current best one
                    if (CurrentSolutionTotalArea > Board.PackedPartsTotalArea)
                    {
                        Board.PackedParts          = CurrentSolution.Clone() as Part[];
                        Board.PackedPartdLengths   = CurrentSolutionDLengths.Clone() as double[];
                        Board.PackedPartdWidths    = CurrentSolutionDWidths.Clone() as double[];
                        Board.PackedPartsCount     = CurrentSolutionPartCount;
                        Board.PackedPartsTotalArea = CurrentSolutionTotalArea;
                    }
                    #endregion

                    #region // Break association and adjust associated boards if a board is used that is associated with another to prevent overlapping placements ...
                    StockItem iAssocBoardSection = iBoardSection.AssociatedBoard;
                    double    oAssocLength       = 0,
                              oAssocWidth        = 0,
                              oiBoardLength      = 0,
                              oiBoardWidth       = 0;

                    // if the board section used has a buddy from a previous placement
                    if (iAssocBoardSection != null)
                    {
                        // keep old sizes so we can revert them at the end of the iteration
                        oAssocLength  = iAssocBoardSection.Length;
                        oAssocWidth   = iAssocBoardSection.Width;
                        oiBoardLength = iBoardSection.Length;
                        oiBoardWidth  = iBoardSection.Width;

                        // if the part was placed on rem1 (the left most board section)
                        if (iBoardSection.dWidth < iAssocBoardSection.dWidth)
                        {
                            //if the part overlaps into rem2
                            if (iBoardSection.dWidth + iPart.Width + sawkerf > iAssocBoardSection.dWidth)
                            {
                                // adjust the length of rem2 so it does not overlap this part
                                iAssocBoardSection.Length -= (iBoardSection.Length + sawkerf);
                            }
                            else
                            {
                                // adjust rem1 so it does not overlap rem2
                                iBoardSection.Width -= (iAssocBoardSection.Width + sawkerf);
                            }
                        }
                        else
                        {
                            // ...part was placed on rem2 (the right most board section)
                            // if the part overlaps onto rem1
                            if (iBoardSection.dLength + iPart.Length + sawkerf > iAssocBoardSection.dLength)
                            {
                                // adjust rem1 so it does not overlap the part
                                iAssocBoardSection.Width -= (iBoardSection.Width + sawkerf);
                            }
                            else
                            {
                                // adjust rem2 so it does not overlap rem1
                                iBoardSection.Length -= (iAssocBoardSection.Length + sawkerf);
                            }
                        }
                        // break the association
                        iAssocBoardSection.AssociatedBoard = null;
                        iBoardSection.AssociatedBoard      = null;
                    }
                    #endregion

                    #region // replace the used board with 2 overlapping remainder pieces after subtracting the part ...
                    // create new sections
                    StockItem boardSection1 = new StockItem(iBoardSection.Name, iBoardSection.Length - iPart.Length - sawkerf, iBoardSection.Width, iBoardSection.dLength + iPart.Length + sawkerf, iBoardSection.dWidth);
                    StockItem boardSection2 = new StockItem(iBoardSection.Name, iBoardSection.Length, iBoardSection.Width - iPart.Width - sawkerf, iBoardSection.dLength, iBoardSection.dWidth + iPart.Width + sawkerf);
                    boardSection1.AssociatedBoard = boardSection2;
                    boardSection2.AssociatedBoard = boardSection1;
                    int boardSection1Index = 0;
                    int boardSection2Index = 0;

                    if (boardSection1.Area > Parts[0].Area)
                    {
                        // insert the new rem1 section so the boardsections remain sorted by area
                        for (boardSection1Index = BoardSectionsCount; ; boardSection1Index--)
                        {
                            if (boardSection1Index > 0 && BoardSections[boardSection1Index - 1].Area > boardSection1.Area)
                            {
                                BoardSections[boardSection1Index] = BoardSections[boardSection1Index - 1];
                            }
                            else
                            {
                                BoardSections[boardSection1Index] = boardSection1;
                                break;
                            }
                        }
                        BoardSectionsCount++;
                    }
                    else
                    {
                        boardSection1                 = null;
                        boardSection1Index            = BoardSectionsCount + 999;
                        boardSection2.AssociatedBoard = null;
                    }


                    if (boardSection2.Area > Parts[0].Area)
                    {
                        // insert the new rem2 section so the boardsections remain sorted by area
                        for (boardSection2Index = BoardSectionsCount; ; boardSection2Index--)
                        {
                            if (boardSection2Index > 0 && BoardSections[boardSection2Index - 1].Area > boardSection2.Area)
                            {
                                BoardSections[boardSection2Index] = BoardSections[boardSection2Index - 1];
                            }
                            else
                            {
                                BoardSections[boardSection2Index] = boardSection2;
                                break;
                            }
                        }
                        BoardSectionsCount++;
                    }
                    else
                    {
                        boardSection2      = null;
                        boardSection2Index = BoardSectionsCount + 999;
                        if (boardSection1 != null)
                        {
                            boardSection1.AssociatedBoard = null;
                        }
                    }

                    #endregion
#if DEBUG
                    //Drawboard_debug(
                    //    Board,
                    //    BoardSections, BoardSectionsCount,
                    //    CurrentSolution, CurrentSolutionDLengths, CurrentSolutionDWidths, CurrentSolutionPartCount, CurrentSolutionTotalArea).Save($"dbgimages\\{Board.Name}_{cn++}.bmp");
#endif
                    #region // pack the remaining parts on the remaining boards ...
                    // pack the remaining parts on the remaining boards
                    if (i + 1 < PartsCount)
                    {
                        StartPacking(i + 1);
                    }
                    #endregion

                    #region // undo the placement so we can iterate to the next part and test with it ...

                    // remove the remainder board sections we added...
                    if (boardSection2Index < BoardSectionsCount)
                    {
                        for (int irem = boardSection2Index; irem < BoardSectionsCount; irem++)
                        {
                            BoardSections[irem] = BoardSections[irem + 1];
                        }
                        BoardSectionsCount--;
                    }

                    if (boardSection1Index < BoardSectionsCount)
                    {
                        for (int irem = boardSection1Index; irem < BoardSectionsCount; irem++)
                        {
                            BoardSections[irem] = BoardSections[irem + 1];
                        }
                        BoardSectionsCount--;
                    }

                    // restore associations, and the original associated board sections' sizes
                    if (iAssocBoardSection != null)
                    {
                        iBoardSection.AssociatedBoard      = iAssocBoardSection;
                        iAssocBoardSection.AssociatedBoard = iBoardSection;
                        iAssocBoardSection.Length          = oAssocLength;
                        iAssocBoardSection.Width           = oAssocWidth;
                        iBoardSection.Length = oiBoardLength;
                        iBoardSection.Width  = oiBoardWidth;
                    }

                    // place the board back in play
                    iBoardSection.isInUse = false;

                    // remove the part from the temporary solution
                    CurrentSolution[--CurrentSolutionPartCount] = null;
                    CurrentSolutionTotalArea -= iPart.Area;

                    #endregion
                }
            }