- Add verbose per-file and per-sheet-size console output during collection - Skip already-processed parts at the sheet-size level instead of all-or-nothing - Precompute best-fits once per part and reuse across all sheet sizes - Clear best-fit cache after each part to prevent memory growth - Save best-fits in separate bestfits/ zip entries instead of embedding in nest.json - Filter to Keep=true results only and scope to plate sizes in the nest - Set nest name to match filename (includes sheet size and part count) - Add TrainingDatabase with per-run skip logic and SQLite schema Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
146 lines
5.8 KiB
C#
146 lines
5.8 KiB
C#
using System;
|
|
using Microsoft.Data.Sqlite;
|
|
using OpenNest.Engine.ML;
|
|
|
|
namespace OpenNest.Console
|
|
{
|
|
public class TrainingDatabase : IDisposable
|
|
{
|
|
private readonly SqliteConnection _connection;
|
|
|
|
public TrainingDatabase(string dbPath)
|
|
{
|
|
var connectionString = new SqliteConnectionStringBuilder
|
|
{
|
|
DataSource = dbPath,
|
|
Mode = SqliteOpenMode.ReadWriteCreate
|
|
}.ToString();
|
|
|
|
_connection = new SqliteConnection(connectionString);
|
|
_connection.Open();
|
|
|
|
InitializeSchema();
|
|
}
|
|
|
|
private void InitializeSchema()
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = @"
|
|
CREATE TABLE IF NOT EXISTS Parts (
|
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
FileName TEXT,
|
|
Area REAL,
|
|
Convexity REAL,
|
|
AspectRatio REAL,
|
|
BBFill REAL,
|
|
Circularity REAL,
|
|
VertexCount INTEGER,
|
|
Bitmask BLOB,
|
|
GeometryData TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS Runs (
|
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
PartId INTEGER,
|
|
SheetWidth REAL,
|
|
SheetHeight REAL,
|
|
Spacing REAL,
|
|
PartCount INTEGER,
|
|
Utilization REAL,
|
|
TimeMs INTEGER,
|
|
LayoutData TEXT,
|
|
FilePath TEXT,
|
|
FOREIGN KEY(PartId) REFERENCES Parts(Id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_parts_filename ON Parts(FileName);
|
|
CREATE INDEX IF NOT EXISTS idx_runs_partid ON Runs(PartId);
|
|
";
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
public long GetOrAddPart(string fileName, PartFeatures features, string geometryData)
|
|
{
|
|
// Check if part already exists
|
|
using (var checkCmd = _connection.CreateCommand())
|
|
{
|
|
checkCmd.CommandText = "SELECT Id FROM Parts WHERE FileName = @name";
|
|
checkCmd.Parameters.AddWithValue("@name", fileName);
|
|
var result = checkCmd.ExecuteScalar();
|
|
if (result != null) return (long)result;
|
|
}
|
|
|
|
// Add new part
|
|
using (var insertCmd = _connection.CreateCommand())
|
|
{
|
|
insertCmd.CommandText = @"
|
|
INSERT INTO Parts (FileName, Area, Convexity, AspectRatio, BBFill, Circularity, VertexCount, Bitmask, GeometryData)
|
|
VALUES (@name, @area, @conv, @asp, @fill, @circ, @vert, @mask, @geo);
|
|
SELECT last_insert_rowid();";
|
|
|
|
insertCmd.Parameters.AddWithValue("@name", fileName);
|
|
insertCmd.Parameters.AddWithValue("@area", features.Area);
|
|
insertCmd.Parameters.AddWithValue("@conv", features.Convexity);
|
|
insertCmd.Parameters.AddWithValue("@asp", features.AspectRatio);
|
|
insertCmd.Parameters.AddWithValue("@fill", features.BoundingBoxFill);
|
|
insertCmd.Parameters.AddWithValue("@circ", features.Circularity);
|
|
insertCmd.Parameters.AddWithValue("@vert", features.VertexCount);
|
|
insertCmd.Parameters.AddWithValue("@mask", features.Bitmask);
|
|
insertCmd.Parameters.AddWithValue("@geo", geometryData);
|
|
|
|
return (long)insertCmd.ExecuteScalar();
|
|
}
|
|
}
|
|
|
|
public bool HasRun(string fileName, double sheetWidth, double sheetHeight, double spacing)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = @"SELECT COUNT(*) FROM Runs r JOIN Parts p ON r.PartId = p.Id
|
|
WHERE p.FileName = @name AND r.SheetWidth = @w AND r.SheetHeight = @h AND r.Spacing = @s";
|
|
cmd.Parameters.AddWithValue("@name", fileName);
|
|
cmd.Parameters.AddWithValue("@w", sheetWidth);
|
|
cmd.Parameters.AddWithValue("@h", sheetHeight);
|
|
cmd.Parameters.AddWithValue("@s", spacing);
|
|
return (long)cmd.ExecuteScalar() > 0;
|
|
}
|
|
|
|
public int RunCount(string fileName)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = "SELECT COUNT(*) FROM Runs r JOIN Parts p ON r.PartId = p.Id WHERE p.FileName = @name";
|
|
cmd.Parameters.AddWithValue("@name", fileName);
|
|
return (int)(long)cmd.ExecuteScalar();
|
|
}
|
|
|
|
public void AddRun(long partId, double w, double h, double s, BruteForceResult result, string filePath)
|
|
{
|
|
using var cmd = _connection.CreateCommand();
|
|
cmd.CommandText = @"
|
|
INSERT INTO Runs (PartId, SheetWidth, SheetHeight, Spacing, PartCount, Utilization, TimeMs, LayoutData, FilePath)
|
|
VALUES (@pid, @w, @h, @s, @cnt, @util, @time, @layout, @path)";
|
|
|
|
cmd.Parameters.AddWithValue("@pid", partId);
|
|
cmd.Parameters.AddWithValue("@w", w);
|
|
cmd.Parameters.AddWithValue("@h", h);
|
|
cmd.Parameters.AddWithValue("@s", s);
|
|
cmd.Parameters.AddWithValue("@cnt", result.PartCount);
|
|
cmd.Parameters.AddWithValue("@util", result.Utilization);
|
|
cmd.Parameters.AddWithValue("@time", result.TimeMs);
|
|
cmd.Parameters.AddWithValue("@layout", result.LayoutData ?? "");
|
|
cmd.Parameters.AddWithValue("@path", filePath ?? "");
|
|
|
|
cmd.ExecuteNonQuery();
|
|
}
|
|
|
|
public SqliteTransaction BeginTransaction()
|
|
{
|
|
return _connection.BeginTransaction();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_connection?.Dispose();
|
|
}
|
|
}
|
|
}
|