diff --git a/OpenNest.Engine/FillLinear.cs b/OpenNest.Engine/FillLinear.cs index f619434..431947d 100644 --- a/OpenNest.Engine/FillLinear.cs +++ b/OpenNest.Engine/FillLinear.cs @@ -325,6 +325,7 @@ namespace OpenNest /// /// Recursively fills the work area. At depth 0, tiles the pattern along the /// primary axis, then recurses perpendicular. At depth 1, tiles and returns. + /// After the grid is formed, fills the remaining strip with individual parts. /// private List FillRecursive(Pattern pattern, NestDirection direction, int depth) { @@ -337,7 +338,16 @@ namespace OpenNest var rowPattern = new Pattern(); rowPattern.Parts.AddRange(result); rowPattern.UpdateBounds(); - return FillRecursive(rowPattern, PerpendicularAxis(direction), depth + 1); + var gridResult = FillRecursive(rowPattern, PerpendicularAxis(direction), depth + 1); + + // Fill the remaining strip (after the last full row/column) + // with individual parts from the seed pattern. + var remaining = FillRemainingStrip(gridResult, pattern, PerpendicularAxis(direction), direction); + + if (remaining.Count > 0) + gridResult.AddRange(remaining); + + return gridResult; } if (depth == 0) @@ -349,6 +359,118 @@ namespace OpenNest return result; } + /// + /// After tiling full rows/columns, fills the remaining strip with individual + /// parts. The strip is the leftover space along the tiled axis between the + /// last full row/column and the work area boundary. Each unique drawing and + /// rotation from the seed pattern is tried in both directions. + /// + private List FillRemainingStrip( + List placedParts, Pattern seedPattern, + NestDirection tiledAxis, NestDirection primaryAxis) + { + // Find the furthest edge of placed parts along the tiled axis. + var placedEdge = double.MinValue; + + foreach (var part in placedParts) + { + var edge = tiledAxis == NestDirection.Vertical + ? part.BoundingBox.Top + : part.BoundingBox.Right; + + if (edge > placedEdge) + placedEdge = edge; + } + + // Build the remaining strip with a spacing gap from the last tiled row. + Box remainingStrip; + + if (tiledAxis == NestDirection.Vertical) + { + var bottom = placedEdge + PartSpacing; + var height = WorkArea.Top - bottom; + + if (height <= Tolerance.Epsilon) + return new List(); + + remainingStrip = new Box(WorkArea.X, bottom, WorkArea.Width, height); + } + else + { + var left = placedEdge + PartSpacing; + var width = WorkArea.Right - left; + + if (width <= Tolerance.Epsilon) + return new List(); + + remainingStrip = new Box(left, WorkArea.Y, width, WorkArea.Height); + } + + // Build rotation set: always try cardinal orientations (0° and 90°), + // plus any unique rotations from the seed pattern. + var filler = new FillLinear(remainingStrip, PartSpacing); + List best = null; + var rotations = new List<(Drawing drawing, double rotation)>(); + + // Cardinal rotations for each unique drawing. + var drawings = new List(); + + foreach (var seedPart in seedPattern.Parts) + { + var found = false; + + foreach (var d in drawings) + { + if (d == seedPart.BaseDrawing) + { + found = true; + break; + } + } + + if (!found) + drawings.Add(seedPart.BaseDrawing); + } + + foreach (var drawing in drawings) + { + rotations.Add((drawing, 0)); + rotations.Add((drawing, Angle.HalfPI)); + } + + // Add seed pattern rotations that aren't already covered. + foreach (var seedPart in seedPattern.Parts) + { + var skip = false; + + foreach (var (d, r) in rotations) + { + if (d == seedPart.BaseDrawing && r.IsEqualTo(seedPart.Rotation)) + { + skip = true; + break; + } + } + + if (!skip) + rotations.Add((seedPart.BaseDrawing, seedPart.Rotation)); + } + + foreach (var (drawing, rotation) in rotations) + { + var h = filler.Fill(drawing, rotation, NestDirection.Horizontal); + var v = filler.Fill(drawing, rotation, NestDirection.Vertical); + + if (h != null && h.Count > 0 && (best == null || h.Count > best.Count)) + best = h; + + if (v != null && v.Count > 0 && (best == null || v.Count > best.Count)) + best = v; + } + + return best ?? new List(); + } + /// /// Fills a single row of identical parts along one axis using geometry-aware spacing. ///