mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-11 04:11:09 -05:00
feat(cad-converter): add a Hole Table window that counts and colors holes by size
This commit is contained in:
1 parent
45ab837c8c
commit
ad5f2a9041
9 files changed
+566
-1
No files matched your search
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Dimensions
|
||||
{
|
||||
/// <summary>One hole diameter in a <see cref="HoleTable"/> and every hole cut at that size.</summary>
|
||||
public sealed class HoleSize
|
||||
{
|
||||
public HoleSize(double diameter, IReadOnlyList<Vector> centers, IReadOnlyList<Guid> entityIds)
|
||||
{
|
||||
Diameter = diameter;
|
||||
Centers = centers;
|
||||
EntityIds = entityIds;
|
||||
}
|
||||
|
||||
/// <summary>The diameter, rounded to <see cref="HoleTable.Decimals"/> places.</summary>
|
||||
public double Diameter { get; }
|
||||
|
||||
public int Quantity => Centers.Count;
|
||||
|
||||
public IReadOnlyList<Vector> Centers { get; }
|
||||
|
||||
/// <summary>The IDs of every entity that makes up a hole of this size.</summary>
|
||||
public IReadOnlyList<Guid> EntityIds { get; }
|
||||
|
||||
public string DiameterText => "\u00D8" + Diameter.ToString("0.0000", System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the round holes in a drawing by diameter. A round hole is a cut circle, or a closed
|
||||
/// contour made only of arcs on one center and radius, that lies inside another cut contour, so
|
||||
/// the outline of a round part is not a hole. Marks (scribe, etch, rapids, break cuts) are not
|
||||
/// holes. Pass the entities as they will be cut (visible, with roles applied).
|
||||
/// </summary>
|
||||
public static class HoleTable
|
||||
{
|
||||
/// <summary>Holes whose diameters agree to this many decimal places share a row.</summary>
|
||||
public const int Decimals = 4;
|
||||
|
||||
public static IReadOnlyList<HoleSize> Build(IEnumerable<Entity> entities)
|
||||
{
|
||||
var cut = entities
|
||||
.Where(e => SpecialLayers.IsMaterial(e.Layer) && !ScribeLayerRepair.IsMarkLayer(e.Layer))
|
||||
.ToList();
|
||||
if (cut.Count == 0)
|
||||
return Array.Empty<HoleSize>();
|
||||
|
||||
var shapes = ShapeBuilder.GetShapes(cut);
|
||||
var holes = new List<(double Diameter, Vector Center, List<Guid> Ids)>();
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
if (!TryRound(shape, out var center, out var radius))
|
||||
continue;
|
||||
var box = shape.BoundingBox;
|
||||
var area = box.Area();
|
||||
if (!shapes.Any(other => !ReferenceEquals(other, shape)
|
||||
&& other.BoundingBox.Area() > area + Tolerance.Epsilon
|
||||
&& other.BoundingBox.Contains(box)))
|
||||
continue;
|
||||
holes.Add((System.Math.Round(radius * 2, Decimals), center, shape.Entities.Select(e => e.Id).ToList()));
|
||||
}
|
||||
|
||||
return holes
|
||||
.GroupBy(h => h.Diameter)
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => new HoleSize(
|
||||
g.Key,
|
||||
g.Select(h => h.Center).ToList(),
|
||||
g.SelectMany(h => h.Ids).ToList()))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static bool TryRound(Shape shape, out Vector center, out double radius)
|
||||
{
|
||||
center = default;
|
||||
radius = 0;
|
||||
if (shape.Entities.Count == 1 && shape.Entities[0] is Circle circle)
|
||||
{
|
||||
center = circle.Center;
|
||||
radius = circle.Radius;
|
||||
return radius > 0;
|
||||
}
|
||||
|
||||
if (shape.Entities.Count == 0 || !shape.Entities.All(e => e is Arc) || !shape.IsClosed())
|
||||
return false;
|
||||
|
||||
var arcs = shape.Entities.Cast<Arc>().ToList();
|
||||
var first = arcs[0];
|
||||
var tolerance = Tolerance.ChainTolerance;
|
||||
if (arcs.Any(a => a.Center.DistanceTo(first.Center) > tolerance
|
||||
|| System.Math.Abs(a.Radius - first.Radius) > tolerance))
|
||||
return false;
|
||||
if (System.Math.Abs(arcs.Sum(a => a.SweepAngle()) - Angle.TwoPI) > 1e-6)
|
||||
return false;
|
||||
|
||||
center = first.Center;
|
||||
radius = first.Radius;
|
||||
return radius > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using OpenNest.Dimensions;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Tests.Dimensions;
|
||||
|
||||
public class HoleTableTests
|
||||
{
|
||||
private static readonly Layer Cut = new("0") { IsVisible = true };
|
||||
|
||||
private static List<Entity> Rectangle(double width, double height) =>
|
||||
[
|
||||
new Line(0, 0, width, 0) { Layer = Cut },
|
||||
new Line(width, 0, width, height) { Layer = Cut },
|
||||
new Line(width, height, 0, height) { Layer = Cut },
|
||||
new Line(0, height, 0, 0) { Layer = Cut },
|
||||
];
|
||||
|
||||
private static Circle Hole(double x, double y, double diameter, Layer layer = null) =>
|
||||
new(x, y, diameter / 2) { Layer = layer ?? Cut };
|
||||
|
||||
[Fact]
|
||||
public void Build_GroupsHolesByDiameter_SmallestFirst_WithEveryEntityId()
|
||||
{
|
||||
var a = Hole(1, 1, 0.5);
|
||||
var b = Hole(3, 1, 0.5);
|
||||
var c = Hole(5, 1, 0.25);
|
||||
var entities = Rectangle(10, 4);
|
||||
entities.AddRange([a, b, c]);
|
||||
|
||||
var table = HoleTable.Build(entities);
|
||||
|
||||
Assert.Equal(2, table.Count);
|
||||
Assert.Equal(0.25, table[0].Diameter, 12);
|
||||
Assert.Equal(1, table[0].Quantity);
|
||||
Assert.Equal([c.Id], table[0].EntityIds);
|
||||
Assert.Equal(0.5, table[1].Diameter, 12);
|
||||
Assert.Equal(2, table[1].Quantity);
|
||||
Assert.Equal(new[] { a.Id, b.Id }.Order(), table[1].EntityIds.Order());
|
||||
Assert.Equal("\u00D80.5000", table[1].DiameterText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_CountsAHoleDrawnAsTwoArcs_AsOneHole()
|
||||
{
|
||||
var top = new Arc(5, 2, 0.375, 0, Angle.ToRadians(180)) { Layer = Cut };
|
||||
var bottom = new Arc(5, 2, 0.375, Angle.ToRadians(180), Angle.TwoPI) { Layer = Cut };
|
||||
var entities = Rectangle(10, 4);
|
||||
entities.AddRange([top, bottom]);
|
||||
|
||||
var table = HoleTable.Build(entities);
|
||||
|
||||
var size = Assert.Single(table);
|
||||
Assert.Equal(0.75, size.Diameter, 12);
|
||||
Assert.Equal(1, size.Quantity);
|
||||
Assert.Equal(new[] { top.Id, bottom.Id }.Order(), size.EntityIds.Order());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_SharesARow_WhenDiametersAgreeToFourPlaces()
|
||||
{
|
||||
var entities = Rectangle(10, 4);
|
||||
entities.AddRange([Hole(1, 1, 0.25001), Hole(3, 1, 0.24999), Hole(5, 1, 0.2502)]);
|
||||
|
||||
var table = HoleTable.Build(entities);
|
||||
|
||||
Assert.Equal([2, 1], table.Select(s => s.Quantity));
|
||||
Assert.Equal(0.25, table[0].Diameter, 12);
|
||||
Assert.Equal(0.2502, table[1].Diameter, 12);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_DoesNotCountTheOutlineOfARoundPart_OrMarks_OrSlots()
|
||||
{
|
||||
var outline = Hole(0, 0, 6);
|
||||
var hole = Hole(0, 0, 1);
|
||||
var etched = Hole(1.5, 0, 0.5, SpecialLayers.Scribe);
|
||||
var slot = new List<Entity>
|
||||
{
|
||||
new Arc(-2, 1, 0.25, Angle.ToRadians(90), Angle.ToRadians(270)) { Layer = Cut },
|
||||
new Line(-2, 0.75, -1, 0.75) { Layer = Cut },
|
||||
new Arc(-1, 1, 0.25, Angle.ToRadians(270), Angle.ToRadians(90)) { Layer = Cut },
|
||||
new Line(-1, 1.25, -2, 1.25) { Layer = Cut },
|
||||
};
|
||||
var entities = new List<Entity> { outline, hole, etched };
|
||||
entities.AddRange(slot);
|
||||
|
||||
var table = HoleTable.Build(entities);
|
||||
|
||||
var size = Assert.Single(table);
|
||||
Assert.Equal(1, size.Diameter, 12);
|
||||
Assert.Equal([hole.Id], size.EntityIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_IsEmpty_WithNoGeometry()
|
||||
{
|
||||
Assert.Empty(HoleTable.Build([]));
|
||||
Assert.Empty(HoleTable.Build(Rectangle(4, 4)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Drawing;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Forms;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
|
||||
namespace OpenNest.WinForms.Tests.Forms;
|
||||
|
||||
/// <summary>
|
||||
/// The CAD Converter's Hole Table on a 10 x 6 plate with two 0.5 holes and one 0.25 hole.
|
||||
/// </summary>
|
||||
public class CadConverterHoleTableTests
|
||||
{
|
||||
[Fact]
|
||||
public void Holes_ListsEachSizeWithItsQuantity_AndColorsItsHolesOnTheView() => ArchUnitsTests.RunSta(() =>
|
||||
{
|
||||
using var form = new CadConverterForm();
|
||||
Show(form);
|
||||
form.LoadDrawings([Part()]);
|
||||
var view = Field<EntityView>(form, "entityView1");
|
||||
var item = Field<FileListControl>(form, "fileList").SelectedItem!;
|
||||
var circles = item.Entities.OfType<Circle>().ToList();
|
||||
var large = circles.Where(c => c.Radius > 0.2).Select(c => c.Id).ToList();
|
||||
var small = circles.Single(c => c.Radius < 0.2).Id;
|
||||
|
||||
Assert.Null(view.HoleColors);
|
||||
Field<Button>(form, "btnHoleTable").PerformClick();
|
||||
|
||||
var table = Assert.Single(form.OwnedForms.OfType<HoleTableForm>());
|
||||
Assert.True(table.Visible);
|
||||
var grid = table.Controls.OfType<DataGridView>().Single();
|
||||
Assert.Equal(
|
||||
new[] { ("\u00D80.2500", "1"), ("\u00D80.5000", "2") },
|
||||
grid.Rows.Cast<DataGridViewRow>().Select(r => (r.Cells[1].Value!.ToString()!, r.Cells[2].Value!.ToString()!)));
|
||||
|
||||
// Each size's holes take its row's swatch color; the outline keeps its own.
|
||||
Assert.Equal(3, view.HoleColors!.Count);
|
||||
Assert.Equal(grid.Rows[0].Cells[0].Style.BackColor, view.HoleColors[small]);
|
||||
Assert.All(large, id => Assert.Equal(grid.Rows[1].Cells[0].Style.BackColor, view.HoleColors[id]));
|
||||
Assert.NotEqual(view.HoleColors[small], view.HoleColors[large[0]]);
|
||||
|
||||
// A hole set to Display is no longer cut, so it leaves the table.
|
||||
Invoke(form, "OnEntityActionRequested", null,
|
||||
new EntityActionEventArgs(EntityAction.Display, [circles.First(c => c.Id == large[0])]));
|
||||
Assert.Equal("1", grid.Rows[1].Cells[2].Value!.ToString());
|
||||
Assert.False(view.HoleColors.ContainsKey(large[0]));
|
||||
|
||||
// Closing the table restores the view's normal colors.
|
||||
table.Close();
|
||||
Application.DoEvents();
|
||||
Assert.Null(view.HoleColors);
|
||||
form.Close();
|
||||
});
|
||||
|
||||
private static Drawing Part()
|
||||
{
|
||||
var entities = new List<Entity>
|
||||
{
|
||||
new Line(0, 0, 10, 0), new Line(10, 0, 10, 6), new Line(10, 6, 0, 6), new Line(0, 6, 0, 0),
|
||||
new Circle(2, 3, 0.25), new Circle(5, 3, 0.25), new Circle(8, 3, 0.125),
|
||||
};
|
||||
var layer = new Layer("0") { IsVisible = true };
|
||||
foreach (var e in entities)
|
||||
e.Layer = layer;
|
||||
return CadImporter.BuildDrawing(new CadImportResult { Entities = entities, Name = "holes" }, entities,
|
||||
new List<Bend>(), 1, null, null);
|
||||
}
|
||||
|
||||
private static void Show(Form form)
|
||||
{
|
||||
form.WindowState = FormWindowState.Normal;
|
||||
form.StartPosition = FormStartPosition.Manual;
|
||||
form.Location = new Point(0, 0);
|
||||
form.Size = new System.Drawing.Size(1200, 800);
|
||||
form.Show();
|
||||
}
|
||||
|
||||
private static T Field<T>(object owner, string name) =>
|
||||
(T)typeof(CadConverterForm).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(owner)!;
|
||||
|
||||
private static void Invoke(CadConverterForm form, string name, params object?[] args) =>
|
||||
typeof(CadConverterForm).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic)!.Invoke(form, args);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
public partial class EntityView
|
||||
{
|
||||
private const float HolePenWidth = 2.5f;
|
||||
private readonly Dictionary<int, Pen> holePenCache = new Dictionary<int, Pen>();
|
||||
private IReadOnlyDictionary<Guid, Color> holeColors;
|
||||
|
||||
/// <summary>
|
||||
/// While the Hole Table is open, the color of each hole's entities by entity ID: holes are
|
||||
/// drawn in their size's color with a heavier line. Null draws every entity normally.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<Guid, Color> HoleColors
|
||||
{
|
||||
get => holeColors;
|
||||
set
|
||||
{
|
||||
holeColors = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private Pen HolePen(Entity entity)
|
||||
{
|
||||
if (holeColors == null || !holeColors.TryGetValue(entity.Id, out var color))
|
||||
return null;
|
||||
|
||||
var argb = color.ToArgb();
|
||||
if (!holePenCache.TryGetValue(argb, out var pen))
|
||||
{
|
||||
pen = new Pen(color, HolePenWidth);
|
||||
holePenCache[argb] = pen;
|
||||
}
|
||||
return pen;
|
||||
}
|
||||
|
||||
private void DisposeHolePens()
|
||||
{
|
||||
foreach (var pen in holePenCache.Values)
|
||||
pen.Dispose();
|
||||
holePenCache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ namespace OpenNest.Controls
|
||||
simplifierHighlightSet != null && simplifierHighlightSet.Contains(entity);
|
||||
var pen = isHighlighted
|
||||
? GetEntityPen(Color.FromArgb(60, entity.Color))
|
||||
: GetEntityPen(entity.Color);
|
||||
: HolePen(entity) ?? GetEntityPen(entity.Color);
|
||||
DrawEntity(e.Graphics, entity, pen);
|
||||
}
|
||||
|
||||
@@ -514,6 +514,7 @@ namespace OpenNest.Controls
|
||||
if (disposing)
|
||||
{
|
||||
ClearPenCache();
|
||||
DisposeHolePens();
|
||||
gridPen.Dispose();
|
||||
labelFont.Dispose();
|
||||
dimensionFont.Dispose();
|
||||
|
||||
+16
@@ -32,6 +32,7 @@ namespace OpenNest.Forms
|
||||
btnSplit = new System.Windows.Forms.Button();
|
||||
btnSimplify = new System.Windows.Forms.Button();
|
||||
btnExportDxf = new System.Windows.Forms.Button();
|
||||
btnHoleTable = new System.Windows.Forms.Button();
|
||||
chkShowOriginal = new System.Windows.Forms.CheckBox();
|
||||
chkLabels = new System.Windows.Forms.CheckBox();
|
||||
lblDetect = new System.Windows.Forms.Label();
|
||||
@@ -171,6 +172,7 @@ namespace OpenNest.Forms
|
||||
detailBar.Controls.Add(btnSplit);
|
||||
detailBar.Controls.Add(btnSimplify);
|
||||
detailBar.Controls.Add(btnExportDxf);
|
||||
detailBar.Controls.Add(btnHoleTable);
|
||||
detailBar.Controls.Add(chkShowOriginal);
|
||||
detailBar.Controls.Add(chkLabels);
|
||||
detailBar.Controls.Add(lblDetect);
|
||||
@@ -286,6 +288,19 @@ namespace OpenNest.Forms
|
||||
btnExportDxf.Text = "Export DXF";
|
||||
btnExportDxf.Click += OnExportDxfClick;
|
||||
//
|
||||
// btnHoleTable
|
||||
//
|
||||
btnHoleTable.AutoSize = true;
|
||||
btnHoleTable.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||
btnHoleTable.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||
btnHoleTable.Location = new System.Drawing.Point(530, 6);
|
||||
btnHoleTable.Margin = new System.Windows.Forms.Padding(0, 0, 10, 0);
|
||||
btnHoleTable.Name = "btnHoleTable";
|
||||
btnHoleTable.Size = new System.Drawing.Size(60, 27);
|
||||
btnHoleTable.TabIndex = 8;
|
||||
btnHoleTable.Text = "Holes...";
|
||||
btnHoleTable.Click += OnHoleTableClick;
|
||||
//
|
||||
// chkShowOriginal
|
||||
//
|
||||
chkShowOriginal.AutoSize = true;
|
||||
@@ -431,6 +446,7 @@ namespace OpenNest.Forms
|
||||
private System.Windows.Forms.Button btnSplit;
|
||||
private System.Windows.Forms.Button btnSimplify;
|
||||
private System.Windows.Forms.Button btnExportDxf;
|
||||
private System.Windows.Forms.Button btnHoleTable;
|
||||
private System.Windows.Forms.CheckBox chkShowOriginal;
|
||||
private System.Windows.Forms.CheckBox chkLabels;
|
||||
private System.Windows.Forms.ComboBox cboBendDetector;
|
||||
|
||||
@@ -263,6 +263,7 @@ namespace OpenNest.Forms
|
||||
|
||||
entityView1.ZoomToFit();
|
||||
CheckSimplifiable(item);
|
||||
RefreshHoleTable();
|
||||
}
|
||||
|
||||
private void CheckSimplifiable(FileListItem item)
|
||||
@@ -323,6 +324,7 @@ namespace OpenNest.Forms
|
||||
entityView1.Entities.Clear();
|
||||
entityView1.DimensionLayouts = Array.Empty<DimensionLayout>();
|
||||
entityView1.Invalidate();
|
||||
RefreshHoleTable();
|
||||
}
|
||||
|
||||
private void OnFilterChanged(object sender, EventArgs e)
|
||||
@@ -337,6 +339,7 @@ namespace OpenNest.Forms
|
||||
SyncSuppressedState(item);
|
||||
RefreshBreakoffs(item);
|
||||
RefreshTabbedCutouts(item);
|
||||
RefreshHoleTable();
|
||||
staleProgram = true;
|
||||
}
|
||||
|
||||
@@ -1287,6 +1290,42 @@ namespace OpenNest.Forms
|
||||
|
||||
lblEntityCount.Text = $"{entities.Count} entities";
|
||||
ResetSimplifyButton();
|
||||
RefreshHoleTable();
|
||||
}
|
||||
|
||||
private HoleTableForm holeTable;
|
||||
|
||||
private void OnHoleTableClick(object sender, EventArgs e)
|
||||
{
|
||||
if (holeTable == null || holeTable.IsDisposed)
|
||||
{
|
||||
holeTable = new HoleTableForm();
|
||||
var corner = entityView1.PointToScreen(new Point(entityView1.Width, 0));
|
||||
holeTable.Location = new Point(corner.X - holeTable.Width - 12, corner.Y + 12);
|
||||
holeTable.FormClosed += (_, _) =>
|
||||
{
|
||||
holeTable = null;
|
||||
entityView1.HoleColors = null;
|
||||
};
|
||||
holeTable.Show(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
holeTable.Activate();
|
||||
}
|
||||
|
||||
RefreshHoleTable();
|
||||
}
|
||||
|
||||
// Lists the current drawing's round holes as they will be cut and colors them on the view.
|
||||
private void RefreshHoleTable()
|
||||
{
|
||||
if (holeTable == null || holeTable.IsDisposed)
|
||||
return;
|
||||
|
||||
var item = CurrentItem;
|
||||
var sizes = item == null ? Array.Empty<HoleSize>() : HoleTable.Build(VisibleEntities(item));
|
||||
entityView1.HoleColors = holeTable.ShowSizes(sizes, item?.Name);
|
||||
}
|
||||
|
||||
private void OnShowOriginalChanged(object sender, EventArgs e)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using OpenNest.Dimensions;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
/// <summary>
|
||||
/// A small tool window listing the current drawing's round holes by diameter, with the
|
||||
/// quantity of each and the color its holes are drawn in on the CAD view.
|
||||
/// </summary>
|
||||
public class HoleTableForm : Form
|
||||
{
|
||||
// Bright on the CAD view's dark background and clear of the reserved colors: magenta
|
||||
// (etch), cyan (display), yellow (bends) and white (ordinary cut entities).
|
||||
private static readonly Color[] Palette =
|
||||
{
|
||||
Color.FromArgb(255, 85, 85),
|
||||
Color.FromArgb(90, 220, 90),
|
||||
Color.FromArgb(80, 160, 255),
|
||||
Color.FromArgb(255, 170, 40),
|
||||
Color.FromArgb(0, 205, 175),
|
||||
Color.FromArgb(190, 140, 255),
|
||||
Color.FromArgb(255, 140, 190),
|
||||
Color.FromArgb(175, 215, 60),
|
||||
Color.FromArgb(205, 135, 85),
|
||||
};
|
||||
|
||||
private readonly DataGridView grid;
|
||||
private readonly Label summary;
|
||||
|
||||
public HoleTableForm()
|
||||
{
|
||||
Text = "Hole Table";
|
||||
FormBorderStyle = FormBorderStyle.SizableToolWindow;
|
||||
ShowInTaskbar = false;
|
||||
StartPosition = FormStartPosition.Manual;
|
||||
Font = new Font("Segoe UI", 9F);
|
||||
ClientSize = new Size(240, 260);
|
||||
MinimumSize = new Size(200, 150);
|
||||
|
||||
grid = new DataGridView
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ReadOnly = true,
|
||||
AllowUserToAddRows = false,
|
||||
AllowUserToDeleteRows = false,
|
||||
AllowUserToResizeRows = false,
|
||||
RowHeadersVisible = false,
|
||||
SelectionMode = DataGridViewSelectionMode.FullRowSelect,
|
||||
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
|
||||
BackgroundColor = SystemColors.Window,
|
||||
BorderStyle = BorderStyle.None,
|
||||
TabStop = false,
|
||||
};
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "Color",
|
||||
HeaderText = "",
|
||||
FillWeight = 20,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable,
|
||||
});
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "Size",
|
||||
HeaderText = "Diameter",
|
||||
FillWeight = 50,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable,
|
||||
});
|
||||
grid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "Quantity",
|
||||
HeaderText = "Qty",
|
||||
FillWeight = 30,
|
||||
SortMode = DataGridViewColumnSortMode.NotSortable,
|
||||
DefaultCellStyle = { Alignment = DataGridViewContentAlignment.MiddleRight },
|
||||
});
|
||||
|
||||
summary = new Label
|
||||
{
|
||||
Dock = DockStyle.Bottom,
|
||||
Height = 40,
|
||||
Padding = new Padding(4, 2, 4, 2),
|
||||
ForeColor = SystemColors.GrayText,
|
||||
};
|
||||
|
||||
Controls.Add(grid);
|
||||
Controls.Add(summary);
|
||||
}
|
||||
|
||||
/// <summary>The color of the hole size at <paramref name="index"/> in the table.</summary>
|
||||
public static Color ColorFor(int index)
|
||||
{
|
||||
if (index < Palette.Length)
|
||||
return Palette[index];
|
||||
|
||||
// Past the palette, step the hue by the golden angle so neighbors stay apart.
|
||||
var hue = (index * 137.508) % 360;
|
||||
return FromHsv(hue, 0.6, 1.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows <paramref name="sizes"/> and returns each hole entity's color for the CAD view.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<Guid, Color> ShowSizes(IReadOnlyList<HoleSize> sizes, string drawingName)
|
||||
{
|
||||
var colors = new Dictionary<Guid, Color>();
|
||||
grid.Rows.Clear();
|
||||
for (var i = 0; i < sizes.Count; i++)
|
||||
{
|
||||
var color = ColorFor(i);
|
||||
var row = grid.Rows[grid.Rows.Add("", sizes[i].DiameterText, sizes[i].Quantity)];
|
||||
var swatch = row.Cells[0].Style;
|
||||
swatch.BackColor = color;
|
||||
swatch.SelectionBackColor = color;
|
||||
foreach (var id in sizes[i].EntityIds)
|
||||
colors[id] = color;
|
||||
}
|
||||
grid.ClearSelection();
|
||||
|
||||
var total = sizes.Sum(s => s.Quantity);
|
||||
Text = string.IsNullOrEmpty(drawingName) ? "Hole Table" : $"Hole Table - {drawingName}";
|
||||
summary.Text = sizes.Count == 0
|
||||
? "No round holes. Slots and other shapes are not listed."
|
||||
: $"{total} hole{(total == 1 ? "" : "s")}, {sizes.Count} size{(sizes.Count == 1 ? "" : "s")}. "
|
||||
+ "Round holes only, in drawing units.";
|
||||
return colors;
|
||||
}
|
||||
|
||||
private static Color FromHsv(double hue, double saturation, double value)
|
||||
{
|
||||
var c = value * saturation;
|
||||
var x = c * (1 - System.Math.Abs((hue / 60) % 2 - 1));
|
||||
var m = value - c;
|
||||
var (r, g, b) = (int)(hue / 60) switch
|
||||
{
|
||||
0 => (c, x, 0.0),
|
||||
1 => (x, c, 0.0),
|
||||
2 => (0.0, c, x),
|
||||
3 => (0.0, x, c),
|
||||
4 => (x, 0.0, c),
|
||||
_ => (c, 0.0, x),
|
||||
};
|
||||
return Color.FromArgb((int)((r + m) * 255), (int)((g + m) * 255), (int)((b + m) * 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,4 +50,23 @@ they do not appear on plates, in PDFs or in DXF export.
|
||||
with an `R` prefix and diameter labels with `Ø`.
|
||||
- Split pieces start with no dimensions, like breakoffs and tabbed cutouts.
|
||||
|
||||
## Hole table
|
||||
|
||||
**Holes...** in the CAD Converter's bottom bar opens a small Hole Table window beside the
|
||||
view. It lists the drawing's round holes by diameter, smallest first, with the quantity of
|
||||
each and a color swatch. While it is open, every hole on the view is drawn in its size's color
|
||||
with a heavier line; closing the window restores the normal colors. The table follows the
|
||||
selected file and updates when entities are cut, etched, set to display, excluded or
|
||||
simplified.
|
||||
|
||||
- A round hole is a cut circle, or a closed contour made only of arcs on one center and
|
||||
radius, that lies inside another cut contour. The outline of a round part is not a hole.
|
||||
Slots and other shapes are not listed.
|
||||
- Holes are read from the entities as they will be cut (visible, with roles applied), before
|
||||
breakoffs and tabbed cutouts. Etched, display-only, excluded and hidden circles are not
|
||||
listed.
|
||||
- Diameters are rounded to four decimals, in the drawing's units; holes that agree to four
|
||||
decimals share a row. `HoleTable` (`OpenNest.Core/Dimensions`) builds the rows.
|
||||
- The table is a view only: it stores nothing in the drawing and never affects the program.
|
||||
|
||||
File format: see [drawing dimensions](nest-file-format.md#drawing-dimensions).
|
||||
Reference in new issue
Block a user