docs: update README with accurate features and add roadmap

Remove NFP pair fitting claim from features (not yet integrated).
Qualify lead-in/lead-out as engine-only (UI coming soon).
Mark --autonest CLI option as experimental. Add Roadmap section
with planned work: NFP nesting, lead-in UI, sheet cut-offs,
post-processors, and shape library UI.

Add documentation maintenance instruction to CLAUDE.md requiring
README.md and CLAUDE.md updates when project structure changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-18 16:45:50 -04:00
parent 442501828a
commit 0cba528591
22 changed files with 24 additions and 12 deletions

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest
{
/// <summary>
/// NFP-based Bottom-Left Fill (BLF) placement engine.
/// Places parts one at a time using feasible regions computed from
/// the Inner-Fit Polygon minus the union of No-Fit Polygons.
/// </summary>
public class BottomLeftFill
{
private readonly Box workArea;
private readonly NfpCache nfpCache;
public BottomLeftFill(Box workArea, NfpCache nfpCache)
{
this.workArea = workArea;
this.nfpCache = nfpCache;
}
/// <summary>
/// Places parts according to the given sequence using NFP-based BLF.
/// Each entry is (drawingId, rotation) determining what to place and how.
/// Returns the list of successfully placed parts with their positions.
/// </summary>
public List<PlacedPart> Fill(List<(int drawingId, double rotation, Drawing drawing)> sequence)
{
var placedParts = new List<PlacedPart>();
foreach (var (drawingId, rotation, drawing) in sequence)
{
var polygon = nfpCache.GetPolygon(drawingId, rotation);
if (polygon == null || polygon.Vertices.Count < 3)
continue;
// Compute IFP for this part inside the work area.
var ifp = InnerFitPolygon.Compute(workArea, polygon);
if (ifp.Vertices.Count < 3)
continue;
// Compute NFPs against all already-placed parts.
var nfps = new Polygon[placedParts.Count];
for (var i = 0; i < placedParts.Count; i++)
{
var placed = placedParts[i];
var nfp = nfpCache.Get(placed.DrawingId, placed.Rotation, drawingId, rotation);
// Translate NFP to the placed part's position.
var translated = TranslatePolygon(nfp, placed.Position);
nfps[i] = translated;
}
// Compute feasible region and find bottom-left point.
var feasible = InnerFitPolygon.ComputeFeasibleRegion(ifp, nfps);
var point = InnerFitPolygon.FindBottomLeftPoint(feasible);
if (double.IsNaN(point.X))
continue;
placedParts.Add(new PlacedPart
{
DrawingId = drawingId,
Rotation = rotation,
Position = point,
Drawing = drawing
});
}
return placedParts;
}
/// <summary>
/// Converts placed parts to OpenNest Part instances positioned on the plate.
/// </summary>
public static List<Part> ToNestParts(List<PlacedPart> placedParts)
{
var parts = new List<Part>(placedParts.Count);
foreach (var placed in placedParts)
{
var part = new Part(placed.Drawing);
if (placed.Rotation != 0)
part.Rotate(placed.Rotation);
part.Location = placed.Position;
parts.Add(part);
}
return parts;
}
/// <summary>
/// Creates a translated copy of a polygon.
/// </summary>
private static Polygon TranslatePolygon(Polygon polygon, Vector offset)
{
var result = new Polygon();
foreach (var v in polygon.Vertices)
result.Vertices.Add(new Vector(v.X + offset.X, v.Y + offset.Y));
return result;
}
}
/// <summary>
/// Represents a part that has been placed by the BLF algorithm.
/// </summary>
public class PlacedPart
{
public int DrawingId { get; set; }
public double Rotation { get; set; }
public Vector Position { get; set; }
public Drawing Drawing { get; set; }
}
}