feat: add BestFitCell control with screen-space text overlay
Extract a BestFitCell subclass from PlateView for the Best-Fit Viewer grid cells. Text metadata is now painted in screen coordinates (after resetting the graphics transform) so it stays fixed regardless of zoom. Parts auto-zoom to fit on every resize. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
82
OpenNest/Controls/BestFitCell.cs
Normal file
82
OpenNest/Controls/BestFitCell.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
public class BestFitCell : PlateView
|
||||
{
|
||||
private string[] metadataLines;
|
||||
|
||||
public BestFitResult Result { get; set; }
|
||||
|
||||
public BestFitCell(ColorScheme colorScheme)
|
||||
: base(colorScheme)
|
||||
{
|
||||
DrawOrigin = false;
|
||||
DrawBounds = false;
|
||||
AllowPan = false;
|
||||
AllowSelect = false;
|
||||
AllowZoom = false;
|
||||
AllowDrop = false;
|
||||
Cursor = Cursors.Hand;
|
||||
}
|
||||
|
||||
public void SetMetadata(BestFitResult result, int rank)
|
||||
{
|
||||
Result = result;
|
||||
|
||||
metadataLines = new[]
|
||||
{
|
||||
string.Format("#{0} {1:F1}x{2:F1} Area={3:F1}",
|
||||
rank, result.BoundingWidth, result.BoundingHeight, result.RotatedArea),
|
||||
string.Format("Util={0:P1} Rot={1:F1}\u00b0",
|
||||
result.Utilization,
|
||||
Angle.ToDegrees(result.OptimalRotation)),
|
||||
result.Keep ? "" : result.Reason
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnResize(System.EventArgs e)
|
||||
{
|
||||
base.OnResize(e);
|
||||
|
||||
if (Plate.Parts.Count > 0)
|
||||
ZoomToFit(false);
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
e.Graphics.SmoothingMode = SmoothingMode.HighSpeed;
|
||||
|
||||
e.Graphics.TranslateTransform(origin.X, origin.Y);
|
||||
DrawPlate(e.Graphics);
|
||||
DrawParts(e.Graphics);
|
||||
e.Graphics.ResetTransform();
|
||||
|
||||
PaintMetadata(e.Graphics);
|
||||
}
|
||||
|
||||
private void PaintMetadata(Graphics g)
|
||||
{
|
||||
if (metadataLines == null)
|
||||
return;
|
||||
|
||||
var font = Font;
|
||||
var brush = Brushes.White;
|
||||
var lineHeight = font.GetHeight(g) + 1;
|
||||
var y = 2f;
|
||||
|
||||
foreach (var line in metadataLines)
|
||||
{
|
||||
if (line.Length == 0)
|
||||
continue;
|
||||
|
||||
g.DrawString(line, font, brush, 2, y);
|
||||
y += lineHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,7 @@ namespace OpenNest.Controls
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void DrawPlate(Graphics g)
|
||||
protected void DrawPlate(Graphics g)
|
||||
{
|
||||
var plateRect = new RectangleF
|
||||
{
|
||||
@@ -441,7 +441,7 @@ namespace OpenNest.Controls
|
||||
plateRect.Height);
|
||||
}
|
||||
|
||||
private void DrawParts(Graphics g)
|
||||
protected void DrawParts(Graphics g)
|
||||
{
|
||||
var viewBounds = new RectangleF(-origin.X, -origin.Y, Width, Height);
|
||||
|
||||
@@ -856,10 +856,13 @@ namespace OpenNest.Controls
|
||||
var stationaryBoxes = new List<Box>(stationaryParts.Count);
|
||||
|
||||
var opposite = Helper.OppositeDirection(direction);
|
||||
var halfSpacing = Plate.PartSpacing / 2;
|
||||
|
||||
foreach (var part in stationaryParts)
|
||||
{
|
||||
stationaryLines.Add(Helper.GetPartLines(part.BasePart, opposite));
|
||||
stationaryLines.Add(halfSpacing > 0
|
||||
? Helper.GetOffsetPartLines(part.BasePart, halfSpacing, opposite)
|
||||
: Helper.GetPartLines(part.BasePart, opposite));
|
||||
stationaryBoxes.Add(part.BoundingBox);
|
||||
}
|
||||
|
||||
@@ -868,9 +871,9 @@ namespace OpenNest.Controls
|
||||
|
||||
foreach (var selected in SelectedParts)
|
||||
{
|
||||
// Get offset lines for the moving part.
|
||||
var movingLines = Plate.PartSpacing > 0
|
||||
? Helper.GetOffsetPartLines(selected.BasePart, Plate.PartSpacing, direction)
|
||||
// Get offset lines for the moving part (half-spacing, symmetric with stationary).
|
||||
var movingLines = halfSpacing > 0
|
||||
? Helper.GetOffsetPartLines(selected.BasePart, halfSpacing, direction)
|
||||
: Helper.GetPartLines(selected.BasePart, direction);
|
||||
|
||||
var movingBox = selected.BoundingBox;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
@@ -85,8 +82,8 @@ namespace OpenNest.Forms
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var result = results[i];
|
||||
var view = CreateCellView(result, drawing, i + 1);
|
||||
gridPanel.Controls.Add(view, i % Columns, i / Columns);
|
||||
var cell = CreateCell(result, drawing, i + 1);
|
||||
gridPanel.Controls.Add(cell, i % Columns, i / Columns);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -99,7 +96,7 @@ namespace OpenNest.Forms
|
||||
total, kept, findMs / 1000.0, sw.Elapsed.TotalSeconds, count);
|
||||
}
|
||||
|
||||
private PlateView CreateCellView(BestFitResult result, Drawing drawing, int rank)
|
||||
private BestFitCell CreateCell(BestFitResult result, Drawing drawing, int rank)
|
||||
{
|
||||
var bgColor = result.Keep ? KeptColor : DroppedColor;
|
||||
|
||||
@@ -114,70 +111,27 @@ namespace OpenNest.Forms
|
||||
EdgeSpacingColor = bgColor
|
||||
};
|
||||
|
||||
var view = new PlateView(colorScheme);
|
||||
view.DrawOrigin = false;
|
||||
view.DrawBounds = false;
|
||||
view.AllowPan = false;
|
||||
view.AllowSelect = false;
|
||||
view.AllowZoom = false;
|
||||
view.AllowDrop = false;
|
||||
view.Dock = DockStyle.Fill;
|
||||
view.Plate.Size = new Geometry.Size(
|
||||
var cell = new BestFitCell(colorScheme);
|
||||
cell.Dock = DockStyle.Fill;
|
||||
cell.Plate.Size = new Geometry.Size(
|
||||
result.BoundingWidth,
|
||||
result.BoundingHeight);
|
||||
|
||||
var parts = result.BuildParts(drawing);
|
||||
|
||||
foreach (var part in parts)
|
||||
view.Plate.Parts.Add(part);
|
||||
cell.Plate.Parts.Add(part);
|
||||
|
||||
view.Paint += (sender, e) =>
|
||||
{
|
||||
PaintMetadata(e.Graphics, view, result, rank);
|
||||
};
|
||||
cell.SetMetadata(result, rank);
|
||||
|
||||
view.Resize += (sender, e) =>
|
||||
{
|
||||
view.ZoomToFit(false);
|
||||
};
|
||||
|
||||
view.DoubleClick += (sender, e) =>
|
||||
cell.DoubleClick += (sender, e) =>
|
||||
{
|
||||
SelectedResult = result;
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
};
|
||||
|
||||
view.Cursor = Cursors.Hand;
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void PaintMetadata(Graphics g, PlateView view, BestFitResult result, int rank)
|
||||
{
|
||||
var font = view.Font;
|
||||
var brush = Brushes.White;
|
||||
var lineHeight = font.GetHeight(g) + 1;
|
||||
|
||||
var lines = new[]
|
||||
{
|
||||
string.Format("#{0} {1:F1}x{2:F1} Area={3:F1}",
|
||||
rank, result.BoundingWidth, result.BoundingHeight, result.RotatedArea),
|
||||
string.Format("Util={0:P1} Rot={1:F1}\u00b0",
|
||||
result.Utilization,
|
||||
Angle.ToDegrees(result.OptimalRotation)),
|
||||
result.Keep ? "" : result.Reason
|
||||
};
|
||||
|
||||
var y = 2f;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Length == 0)
|
||||
continue;
|
||||
g.DrawString(line, font, brush, 2, y);
|
||||
y += lineHeight;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user