fix: simplify Shape.OffsetOutward winding normalization and sync designer
OffsetOutward now normalizes to CW winding before offsetting instead of trial-and-error with bounding box comparison. CadConverterForm designer regenerated with new entityView1 properties. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -579,43 +579,38 @@ namespace OpenNest.Geometry
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Offsets the shape outward by the given distance, detecting winding direction
|
/// Offsets the shape outward by the given distance.
|
||||||
/// to choose the correct offset side. Falls back to the opposite side if the
|
/// Normalizes to CW winding before offsetting Left (which is outward for CW),
|
||||||
/// bounding box shrinks (indicating the offset went inward).
|
/// making the method independent of the original contour winding direction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Shape OffsetOutward(double distance)
|
public Shape OffsetOutward(double distance)
|
||||||
{
|
{
|
||||||
var poly = ToPolygon();
|
var poly = ToPolygon();
|
||||||
var side = poly.Vertices.Count >= 3 && poly.RotationDirection() == RotationType.CW
|
|
||||||
? OffsetSide.Left
|
|
||||||
: OffsetSide.Right;
|
|
||||||
|
|
||||||
var result = OffsetEntity(distance, side) as Shape;
|
if (poly == null || poly.Vertices.Count < 3
|
||||||
|
|| poly.RotationDirection() == RotationType.CW)
|
||||||
|
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||||
|
|
||||||
if (result == null)
|
// Shape is CCW — reverse to CW so Left offset goes outward.
|
||||||
return null;
|
var copy = new Shape();
|
||||||
|
|
||||||
UpdateBounds();
|
for (var i = Entities.Count - 1; i >= 0; i--)
|
||||||
var originalBB = BoundingBox;
|
|
||||||
result.UpdateBounds();
|
|
||||||
var offsetBB = result.BoundingBox;
|
|
||||||
|
|
||||||
if (offsetBB.Width < originalBB.Width || offsetBB.Length < originalBB.Length)
|
|
||||||
{
|
{
|
||||||
Trace.TraceWarning(
|
switch (Entities[i])
|
||||||
"Shape.OffsetOutward: offset shrank bounding box " +
|
{
|
||||||
$"(original={originalBB.Width:F3}x{originalBB.Length:F3}, " +
|
case Line l:
|
||||||
$"offset={offsetBB.Width:F3}x{offsetBB.Length:F3}). " +
|
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||||
"Retrying with opposite side.");
|
break;
|
||||||
|
case Arc a:
|
||||||
var opposite = side == OffsetSide.Left ? OffsetSide.Right : OffsetSide.Left;
|
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||||
var retry = OffsetEntity(distance, opposite) as Shape;
|
break;
|
||||||
|
case Circle c:
|
||||||
if (retry != null)
|
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer });
|
||||||
result = retry;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return copy.OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
+60
-37
@@ -49,41 +49,41 @@ namespace OpenNest.Forms
|
|||||||
((System.ComponentModel.ISupportInitialize)numQuantity).BeginInit();
|
((System.ComponentModel.ISupportInitialize)numQuantity).BeginInit();
|
||||||
bottomPanel1.SuspendLayout();
|
bottomPanel1.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// mainSplit
|
// mainSplit
|
||||||
//
|
//
|
||||||
mainSplit.Dock = System.Windows.Forms.DockStyle.Fill;
|
mainSplit.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
mainSplit.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
|
mainSplit.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
|
||||||
mainSplit.Location = new System.Drawing.Point(0, 0);
|
mainSplit.Location = new System.Drawing.Point(0, 0);
|
||||||
mainSplit.Name = "mainSplit";
|
mainSplit.Name = "mainSplit";
|
||||||
//
|
//
|
||||||
// mainSplit.Panel1
|
// mainSplit.Panel1
|
||||||
//
|
//
|
||||||
mainSplit.Panel1.Controls.Add(sidebarSplit);
|
mainSplit.Panel1.Controls.Add(sidebarSplit);
|
||||||
mainSplit.Panel1MinSize = 200;
|
mainSplit.Panel1MinSize = 200;
|
||||||
//
|
//
|
||||||
// mainSplit.Panel2
|
// mainSplit.Panel2
|
||||||
//
|
//
|
||||||
mainSplit.Panel2.Controls.Add(entityView1);
|
mainSplit.Panel2.Controls.Add(entityView1);
|
||||||
mainSplit.Panel2.Controls.Add(detailBar);
|
mainSplit.Panel2.Controls.Add(detailBar);
|
||||||
mainSplit.Size = new System.Drawing.Size(1024, 670);
|
mainSplit.Size = new System.Drawing.Size(1024, 670);
|
||||||
mainSplit.SplitterDistance = 260;
|
mainSplit.SplitterDistance = 260;
|
||||||
mainSplit.SplitterWidth = 5;
|
mainSplit.SplitterWidth = 5;
|
||||||
mainSplit.TabIndex = 2;
|
mainSplit.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// sidebarSplit
|
// sidebarSplit
|
||||||
//
|
//
|
||||||
sidebarSplit.Dock = System.Windows.Forms.DockStyle.Fill;
|
sidebarSplit.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
sidebarSplit.Location = new System.Drawing.Point(0, 0);
|
sidebarSplit.Location = new System.Drawing.Point(0, 0);
|
||||||
sidebarSplit.Name = "sidebarSplit";
|
sidebarSplit.Name = "sidebarSplit";
|
||||||
sidebarSplit.Orientation = System.Windows.Forms.Orientation.Horizontal;
|
sidebarSplit.Orientation = System.Windows.Forms.Orientation.Horizontal;
|
||||||
//
|
//
|
||||||
// sidebarSplit.Panel1
|
// sidebarSplit.Panel1
|
||||||
//
|
//
|
||||||
sidebarSplit.Panel1.Controls.Add(fileList);
|
sidebarSplit.Panel1.Controls.Add(fileList);
|
||||||
//
|
//
|
||||||
// sidebarSplit.Panel2
|
// sidebarSplit.Panel2
|
||||||
//
|
//
|
||||||
sidebarSplit.Panel2.Controls.Add(filterPanel);
|
sidebarSplit.Panel2.Controls.Add(filterPanel);
|
||||||
sidebarSplit.Size = new System.Drawing.Size(260, 670);
|
sidebarSplit.Size = new System.Drawing.Size(260, 670);
|
||||||
sidebarSplit.SplitterDistance = 300;
|
sidebarSplit.SplitterDistance = 300;
|
||||||
@@ -116,8 +116,15 @@ namespace OpenNest.Forms
|
|||||||
entityView1.BackColor = System.Drawing.Color.FromArgb(33, 40, 48);
|
entityView1.BackColor = System.Drawing.Color.FromArgb(33, 40, 48);
|
||||||
entityView1.Cursor = System.Windows.Forms.Cursors.Cross;
|
entityView1.Cursor = System.Windows.Forms.Cursors.Cross;
|
||||||
entityView1.Dock = System.Windows.Forms.DockStyle.Fill;
|
entityView1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
|
entityView1.IsPickingBendLine = false;
|
||||||
entityView1.Location = new System.Drawing.Point(0, 0);
|
entityView1.Location = new System.Drawing.Point(0, 0);
|
||||||
entityView1.Name = "entityView1";
|
entityView1.Name = "entityView1";
|
||||||
|
entityView1.OriginalEntities = null;
|
||||||
|
entityView1.ShowEntityLabels = false;
|
||||||
|
entityView1.SimplifierHighlight = null;
|
||||||
|
entityView1.SimplifierPreview = null;
|
||||||
|
entityView1.SimplifierToleranceLeft = null;
|
||||||
|
entityView1.SimplifierToleranceRight = null;
|
||||||
entityView1.Size = new System.Drawing.Size(759, 634);
|
entityView1.Size = new System.Drawing.Size(759, 634);
|
||||||
entityView1.TabIndex = 0;
|
entityView1.TabIndex = 0;
|
||||||
//
|
//
|
||||||
@@ -215,52 +222,68 @@ namespace OpenNest.Forms
|
|||||||
//
|
//
|
||||||
btnSplit.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
btnSplit.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||||
btnSplit.Font = new System.Drawing.Font("Segoe UI", 9F);
|
btnSplit.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
btnSplit.Location = new System.Drawing.Point(291, 6);
|
btnSplit.Location = new System.Drawing.Point(289, 6);
|
||||||
btnSplit.Margin = new System.Windows.Forms.Padding(2, 0, 8, 0);
|
btnSplit.Margin = new System.Windows.Forms.Padding(0, 0, 10, 0);
|
||||||
btnSplit.Name = "btnSplit";
|
btnSplit.Name = "btnSplit";
|
||||||
btnSplit.Size = new System.Drawing.Size(60, 24);
|
btnSplit.Size = new System.Drawing.Size(60, 27);
|
||||||
btnSplit.TabIndex = 6;
|
btnSplit.TabIndex = 6;
|
||||||
btnSplit.Text = "Split...";
|
btnSplit.Text = "Split...";
|
||||||
//
|
//
|
||||||
// btnSimplify
|
// btnSimplify
|
||||||
//
|
//
|
||||||
|
btnSimplify.AutoSize = true;
|
||||||
btnSimplify.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
btnSimplify.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||||
btnSimplify.Font = new System.Drawing.Font("Segoe UI", 9F);
|
btnSimplify.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
btnSimplify.Location = new System.Drawing.Point(359, 6);
|
||||||
|
btnSimplify.Margin = new System.Windows.Forms.Padding(0, 0, 10, 0);
|
||||||
|
btnSimplify.Name = "btnSimplify";
|
||||||
|
btnSimplify.Size = new System.Drawing.Size(75, 27);
|
||||||
|
btnSimplify.TabIndex = 7;
|
||||||
btnSimplify.Text = "Simplify...";
|
btnSimplify.Text = "Simplify...";
|
||||||
btnSimplify.AutoSize = true;
|
btnSimplify.Click += OnSimplifyClick;
|
||||||
btnSimplify.Margin = new System.Windows.Forms.Padding(4, 0, 0, 0);
|
//
|
||||||
btnSimplify.Click += new System.EventHandler(this.OnSimplifyClick);
|
|
||||||
//
|
|
||||||
// btnExportDxf
|
// btnExportDxf
|
||||||
//
|
//
|
||||||
|
btnExportDxf.AutoSize = true;
|
||||||
btnExportDxf.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
btnExportDxf.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
|
||||||
btnExportDxf.Font = new System.Drawing.Font("Segoe UI", 9F);
|
btnExportDxf.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
|
btnExportDxf.Location = new System.Drawing.Point(444, 6);
|
||||||
|
btnExportDxf.Margin = new System.Windows.Forms.Padding(0, 0, 10, 0);
|
||||||
|
btnExportDxf.Name = "btnExportDxf";
|
||||||
|
btnExportDxf.Size = new System.Drawing.Size(76, 27);
|
||||||
|
btnExportDxf.TabIndex = 8;
|
||||||
btnExportDxf.Text = "Export DXF";
|
btnExportDxf.Text = "Export DXF";
|
||||||
btnExportDxf.AutoSize = true;
|
btnExportDxf.Click += OnExportDxfClick;
|
||||||
btnExportDxf.Margin = new System.Windows.Forms.Padding(4, 0, 0, 0);
|
//
|
||||||
btnExportDxf.Click += new System.EventHandler(this.OnExportDxfClick);
|
|
||||||
//
|
|
||||||
// chkShowOriginal
|
// chkShowOriginal
|
||||||
//
|
//
|
||||||
chkShowOriginal.AutoSize = true;
|
chkShowOriginal.AutoSize = true;
|
||||||
chkShowOriginal.Font = new System.Drawing.Font("Segoe UI", 9F);
|
chkShowOriginal.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
chkShowOriginal.Text = "Original";
|
chkShowOriginal.Location = new System.Drawing.Point(536, 9);
|
||||||
chkShowOriginal.Margin = new System.Windows.Forms.Padding(6, 3, 0, 0);
|
chkShowOriginal.Margin = new System.Windows.Forms.Padding(6, 3, 0, 0);
|
||||||
chkShowOriginal.CheckedChanged += new System.EventHandler(this.OnShowOriginalChanged);
|
chkShowOriginal.Name = "chkShowOriginal";
|
||||||
//
|
chkShowOriginal.Size = new System.Drawing.Size(68, 19);
|
||||||
|
chkShowOriginal.TabIndex = 9;
|
||||||
|
chkShowOriginal.Text = "Original";
|
||||||
|
chkShowOriginal.CheckedChanged += OnShowOriginalChanged;
|
||||||
|
//
|
||||||
// chkLabels
|
// chkLabels
|
||||||
//
|
//
|
||||||
chkLabels.AutoSize = true;
|
chkLabels.AutoSize = true;
|
||||||
chkLabels.Font = new System.Drawing.Font("Segoe UI", 9F);
|
chkLabels.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
chkLabels.Text = "Labels";
|
chkLabels.Location = new System.Drawing.Point(610, 9);
|
||||||
chkLabels.Margin = new System.Windows.Forms.Padding(6, 3, 0, 0);
|
chkLabels.Margin = new System.Windows.Forms.Padding(6, 3, 0, 0);
|
||||||
chkLabels.CheckedChanged += new System.EventHandler(this.OnLabelsChanged);
|
chkLabels.Name = "chkLabels";
|
||||||
//
|
chkLabels.Size = new System.Drawing.Size(59, 19);
|
||||||
|
chkLabels.TabIndex = 10;
|
||||||
|
chkLabels.Text = "Labels";
|
||||||
|
chkLabels.CheckedChanged += OnLabelsChanged;
|
||||||
|
//
|
||||||
// lblDetect
|
// lblDetect
|
||||||
//
|
//
|
||||||
lblDetect.AutoSize = true;
|
lblDetect.AutoSize = true;
|
||||||
lblDetect.Font = new System.Drawing.Font("Segoe UI", 9F);
|
lblDetect.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
lblDetect.Location = new System.Drawing.Point(361, 9);
|
lblDetect.Location = new System.Drawing.Point(671, 9);
|
||||||
lblDetect.Margin = new System.Windows.Forms.Padding(2, 3, 0, 0);
|
lblDetect.Margin = new System.Windows.Forms.Padding(2, 3, 0, 0);
|
||||||
lblDetect.Name = "lblDetect";
|
lblDetect.Name = "lblDetect";
|
||||||
lblDetect.Size = new System.Drawing.Size(42, 15);
|
lblDetect.Size = new System.Drawing.Size(42, 15);
|
||||||
@@ -271,7 +294,7 @@ namespace OpenNest.Forms
|
|||||||
//
|
//
|
||||||
cboBendDetector.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
cboBendDetector.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
cboBendDetector.Font = new System.Drawing.Font("Segoe UI", 9F);
|
cboBendDetector.Font = new System.Drawing.Font("Segoe UI", 9F);
|
||||||
cboBendDetector.Location = new System.Drawing.Point(405, 6);
|
cboBendDetector.Location = new System.Drawing.Point(715, 6);
|
||||||
cboBendDetector.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0);
|
cboBendDetector.Margin = new System.Windows.Forms.Padding(2, 0, 0, 0);
|
||||||
cboBendDetector.Name = "cboBendDetector";
|
cboBendDetector.Name = "cboBendDetector";
|
||||||
cboBendDetector.Size = new System.Drawing.Size(90, 23);
|
cboBendDetector.Size = new System.Drawing.Size(90, 23);
|
||||||
|
|||||||
Reference in New Issue
Block a user