feat: replace Clipper2 with direct entity splitting in DrawingSplitter
Replace polygon boolean clipping with direct entity splitting using bounding box filtering and exact intersection math. Eliminates Clipper2 precision drift that caused contour gaps (0.0035") breaking area calculation and ShapeBuilder chaining. Also fixes SpikeGrooveSplit: spike depth is now grooveDepth + weldGap (spike protrudes past groove), both V-shapes use same angle formula, and weldGap no longer double-subtracted from tip depth. SplitDrawingForm: fix parameter mapping (GrooveDepth direct from nud, not inflated), remove redundant Spike Depth display, add feature contour preview and trimmed split lines at feature positions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-38
@@ -41,8 +41,6 @@ namespace OpenNest.Forms
|
||||
lblGrooveDepth = new System.Windows.Forms.Label();
|
||||
nudSpikeAngle = new System.Windows.Forms.NumericUpDown();
|
||||
lblSpikeAngle = new System.Windows.Forms.Label();
|
||||
nudSpikeDepth = new System.Windows.Forms.NumericUpDown();
|
||||
lblSpikeDepth = new System.Windows.Forms.Label();
|
||||
grpTabParams = new System.Windows.Forms.GroupBox();
|
||||
nudTabCount = new System.Windows.Forms.NumericUpDown();
|
||||
lblTabCount = new System.Windows.Forms.Label();
|
||||
@@ -85,7 +83,6 @@ namespace OpenNest.Forms
|
||||
((System.ComponentModel.ISupportInitialize)nudSpikeWeldGap).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudGrooveDepth).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudSpikeAngle).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudSpikeDepth).BeginInit();
|
||||
grpTabParams.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)nudTabCount).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudTabHeight).BeginInit();
|
||||
@@ -162,12 +159,10 @@ namespace OpenNest.Forms
|
||||
grpSpikeParams.Controls.Add(lblGrooveDepth);
|
||||
grpSpikeParams.Controls.Add(nudSpikeAngle);
|
||||
grpSpikeParams.Controls.Add(lblSpikeAngle);
|
||||
grpSpikeParams.Controls.Add(nudSpikeDepth);
|
||||
grpSpikeParams.Controls.Add(lblSpikeDepth);
|
||||
grpSpikeParams.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
grpSpikeParams.Location = new System.Drawing.Point(6, 511);
|
||||
grpSpikeParams.Name = "grpSpikeParams";
|
||||
grpSpikeParams.Size = new System.Drawing.Size(191, 159);
|
||||
grpSpikeParams.Size = new System.Drawing.Size(191, 132);
|
||||
grpSpikeParams.TabIndex = 5;
|
||||
grpSpikeParams.TabStop = false;
|
||||
grpSpikeParams.Text = "Spike Parameters";
|
||||
@@ -175,7 +170,7 @@ namespace OpenNest.Forms
|
||||
//
|
||||
// nudSpikePairCount
|
||||
//
|
||||
nudSpikePairCount.Location = new System.Drawing.Point(110, 128);
|
||||
nudSpikePairCount.Location = new System.Drawing.Point(110, 101);
|
||||
nudSpikePairCount.Maximum = new decimal(new int[] { 50, 0, 0, 0 });
|
||||
nudSpikePairCount.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
|
||||
nudSpikePairCount.Name = "nudSpikePairCount";
|
||||
@@ -187,7 +182,7 @@ namespace OpenNest.Forms
|
||||
// lblSpikePairCount
|
||||
//
|
||||
lblSpikePairCount.AutoSize = true;
|
||||
lblSpikePairCount.Location = new System.Drawing.Point(10, 130);
|
||||
lblSpikePairCount.Location = new System.Drawing.Point(10, 103);
|
||||
lblSpikePairCount.Name = "lblSpikePairCount";
|
||||
lblSpikePairCount.Size = new System.Drawing.Size(66, 15);
|
||||
lblSpikePairCount.TabIndex = 5;
|
||||
@@ -196,7 +191,7 @@ namespace OpenNest.Forms
|
||||
// nudSpikeWeldGap
|
||||
//
|
||||
nudSpikeWeldGap.DecimalPlaces = 3;
|
||||
nudSpikeWeldGap.Location = new System.Drawing.Point(110, 74);
|
||||
nudSpikeWeldGap.Location = new System.Drawing.Point(110, 47);
|
||||
nudSpikeWeldGap.Maximum = new decimal(new int[] { 10, 0, 0, 0 });
|
||||
nudSpikeWeldGap.Name = "nudSpikeWeldGap";
|
||||
nudSpikeWeldGap.Size = new System.Drawing.Size(88, 23);
|
||||
@@ -207,7 +202,7 @@ namespace OpenNest.Forms
|
||||
// lblSpikeWeldGap
|
||||
//
|
||||
lblSpikeWeldGap.AutoSize = true;
|
||||
lblSpikeWeldGap.Location = new System.Drawing.Point(10, 76);
|
||||
lblSpikeWeldGap.Location = new System.Drawing.Point(10, 49);
|
||||
lblSpikeWeldGap.Name = "lblSpikeWeldGap";
|
||||
lblSpikeWeldGap.Size = new System.Drawing.Size(61, 15);
|
||||
lblSpikeWeldGap.TabIndex = 6;
|
||||
@@ -216,18 +211,18 @@ namespace OpenNest.Forms
|
||||
// nudGrooveDepth
|
||||
//
|
||||
nudGrooveDepth.DecimalPlaces = 3;
|
||||
nudGrooveDepth.Location = new System.Drawing.Point(110, 47);
|
||||
nudGrooveDepth.Location = new System.Drawing.Point(110, 20);
|
||||
nudGrooveDepth.Minimum = new decimal(new int[] { 1, 0, 0, 131072 });
|
||||
nudGrooveDepth.Name = "nudGrooveDepth";
|
||||
nudGrooveDepth.Size = new System.Drawing.Size(88, 23);
|
||||
nudGrooveDepth.TabIndex = 1;
|
||||
nudGrooveDepth.Value = new decimal(new int[] { 125, 0, 0, 196608 });
|
||||
nudGrooveDepth.Value = new decimal(new int[] { 625, 0, 0, 196608 });
|
||||
nudGrooveDepth.ValueChanged += OnSpikeParamChanged;
|
||||
//
|
||||
// lblGrooveDepth
|
||||
//
|
||||
lblGrooveDepth.AutoSize = true;
|
||||
lblGrooveDepth.Location = new System.Drawing.Point(10, 49);
|
||||
lblGrooveDepth.Location = new System.Drawing.Point(10, 22);
|
||||
lblGrooveDepth.Name = "lblGrooveDepth";
|
||||
lblGrooveDepth.Size = new System.Drawing.Size(83, 15);
|
||||
lblGrooveDepth.TabIndex = 7;
|
||||
@@ -236,7 +231,7 @@ namespace OpenNest.Forms
|
||||
// nudSpikeAngle
|
||||
//
|
||||
nudSpikeAngle.DecimalPlaces = 1;
|
||||
nudSpikeAngle.Location = new System.Drawing.Point(110, 101);
|
||||
nudSpikeAngle.Location = new System.Drawing.Point(110, 74);
|
||||
nudSpikeAngle.Maximum = new decimal(new int[] { 89, 0, 0, 0 });
|
||||
nudSpikeAngle.Minimum = new decimal(new int[] { 10, 0, 0, 0 });
|
||||
nudSpikeAngle.Name = "nudSpikeAngle";
|
||||
@@ -247,32 +242,12 @@ namespace OpenNest.Forms
|
||||
// lblSpikeAngle
|
||||
//
|
||||
lblSpikeAngle.AutoSize = true;
|
||||
lblSpikeAngle.Location = new System.Drawing.Point(10, 103);
|
||||
lblSpikeAngle.Location = new System.Drawing.Point(10, 76);
|
||||
lblSpikeAngle.Name = "lblSpikeAngle";
|
||||
lblSpikeAngle.Size = new System.Drawing.Size(72, 15);
|
||||
lblSpikeAngle.TabIndex = 8;
|
||||
lblSpikeAngle.Text = "Spike Angle:";
|
||||
//
|
||||
// nudSpikeDepth
|
||||
//
|
||||
nudSpikeDepth.DecimalPlaces = 2;
|
||||
nudSpikeDepth.Enabled = false;
|
||||
nudSpikeDepth.Location = new System.Drawing.Point(110, 20);
|
||||
nudSpikeDepth.Minimum = new decimal(new int[] { 1, 0, 0, 131072 });
|
||||
nudSpikeDepth.Name = "nudSpikeDepth";
|
||||
nudSpikeDepth.Size = new System.Drawing.Size(88, 23);
|
||||
nudSpikeDepth.TabIndex = 0;
|
||||
nudSpikeDepth.Value = new decimal(new int[] { 25, 0, 0, 131072 });
|
||||
//
|
||||
// lblSpikeDepth
|
||||
//
|
||||
lblSpikeDepth.AutoSize = true;
|
||||
lblSpikeDepth.Location = new System.Drawing.Point(10, 22);
|
||||
lblSpikeDepth.Name = "lblSpikeDepth";
|
||||
lblSpikeDepth.Size = new System.Drawing.Size(73, 15);
|
||||
lblSpikeDepth.TabIndex = 9;
|
||||
lblSpikeDepth.Text = "Spike Depth:";
|
||||
//
|
||||
// grpTabParams
|
||||
//
|
||||
grpTabParams.Controls.Add(nudTabCount);
|
||||
@@ -674,7 +649,6 @@ namespace OpenNest.Forms
|
||||
((System.ComponentModel.ISupportInitialize)nudSpikeWeldGap).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudGrooveDepth).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudSpikeAngle).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)nudSpikeDepth).EndInit();
|
||||
grpTabParams.ResumeLayout(false);
|
||||
grpTabParams.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)nudTabCount).EndInit();
|
||||
@@ -747,8 +721,6 @@ namespace OpenNest.Forms
|
||||
private System.Windows.Forms.NumericUpDown nudTabCount;
|
||||
|
||||
private System.Windows.Forms.GroupBox grpSpikeParams;
|
||||
private System.Windows.Forms.Label lblSpikeDepth;
|
||||
private System.Windows.Forms.NumericUpDown nudSpikeDepth;
|
||||
private System.Windows.Forms.Label lblSpikeAngle;
|
||||
private System.Windows.Forms.NumericUpDown nudSpikeAngle;
|
||||
private System.Windows.Forms.Label lblSpikePairCount;
|
||||
|
||||
@@ -141,16 +141,8 @@ public partial class SplitDrawingForm : Form
|
||||
pnlPreview.Invalidate();
|
||||
}
|
||||
|
||||
private void UpdateSpikeDepth()
|
||||
{
|
||||
var grooveDepth = (double)nudGrooveDepth.Value;
|
||||
var weldGap = (double)nudSpikeWeldGap.Value;
|
||||
nudSpikeDepth.Value = (decimal)(grooveDepth + weldGap);
|
||||
}
|
||||
|
||||
private void OnSpikeParamChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateSpikeDepth();
|
||||
if (radFitToPlate.Checked)
|
||||
RecalculateAutoSplitLines();
|
||||
pnlPreview.Invalidate();
|
||||
@@ -175,9 +167,9 @@ public partial class SplitDrawingForm : Form
|
||||
else if (radSpike.Checked)
|
||||
{
|
||||
p.Type = SplitType.SpikeGroove;
|
||||
p.SpikeDepth = (double)nudSpikeDepth.Value;
|
||||
p.GrooveDepth = p.SpikeDepth + (double)nudGrooveDepth.Value;
|
||||
p.GrooveDepth = (double)nudGrooveDepth.Value;
|
||||
p.SpikeWeldGap = (double)nudSpikeWeldGap.Value;
|
||||
p.SpikeDepth = p.GrooveDepth + p.SpikeWeldGap;
|
||||
p.SpikeAngle = (double)nudSpikeAngle.Value;
|
||||
p.SpikePairCount = (int)nudSpikePairCount.Value;
|
||||
}
|
||||
@@ -389,23 +381,72 @@ public partial class SplitDrawingForm : Form
|
||||
System.Math.Abs(br.X - tl.X), System.Math.Abs(br.Y - tl.Y));
|
||||
}
|
||||
|
||||
// Split lines
|
||||
// Split lines — trimmed at feature positions with feature contours
|
||||
var parameters = GetCurrentParameters();
|
||||
var feature = GetSplitFeature(parameters.Type);
|
||||
using var splitPen = new Pen(Color.FromArgb(255, 82, 82));
|
||||
splitPen.DashStyle = DashStyle.Dash;
|
||||
using var featurePen = new Pen(Color.FromArgb(200, 255, 82, 82), 1.5f);
|
||||
|
||||
foreach (var sl in _splitLines)
|
||||
{
|
||||
PointF p1, p2;
|
||||
if (sl.Axis == CutOffAxis.Vertical)
|
||||
GetExtent(sl, out var extStart, out var extEnd);
|
||||
var isVert = sl.Axis == CutOffAxis.Vertical;
|
||||
var margin = 10.0;
|
||||
|
||||
if (sl.FeaturePositions.Count == 0 || radStraight.Checked)
|
||||
{
|
||||
p1 = pnlPreview.PointWorldToGraph(sl.Position, _drawingBounds.Bottom - 10);
|
||||
p2 = pnlPreview.PointWorldToGraph(sl.Position, _drawingBounds.Top + 10);
|
||||
// No features — draw one continuous line
|
||||
var p1 = isVert
|
||||
? pnlPreview.PointWorldToGraph(sl.Position, extStart - margin)
|
||||
: pnlPreview.PointWorldToGraph(extStart - margin, sl.Position);
|
||||
var p2 = isVert
|
||||
? pnlPreview.PointWorldToGraph(sl.Position, extEnd + margin)
|
||||
: pnlPreview.PointWorldToGraph(extEnd + margin, sl.Position);
|
||||
g.DrawLine(splitPen, p1, p2);
|
||||
}
|
||||
else
|
||||
{
|
||||
p1 = pnlPreview.PointWorldToGraph(_drawingBounds.Left - 10, sl.Position);
|
||||
p2 = pnlPreview.PointWorldToGraph(_drawingBounds.Right + 10, sl.Position);
|
||||
// Generate feature geometry and draw contours
|
||||
var featureResult = feature.GenerateFeatures(sl, extStart, extEnd, parameters);
|
||||
DrawFeatureEdge(g, featurePen, featureResult.NegativeSideEdge, isVert);
|
||||
DrawFeatureEdge(g, featurePen, featureResult.PositiveSideEdge, isVert);
|
||||
|
||||
// Draw split line in segments between features
|
||||
var halfExt = GetFeatureHalfExtent(parameters);
|
||||
var sorted = new List<double>(sl.FeaturePositions);
|
||||
sorted.Sort();
|
||||
|
||||
var cursor = extStart - margin;
|
||||
foreach (var fc in sorted)
|
||||
{
|
||||
var gapStart = fc - halfExt;
|
||||
if (gapStart > cursor)
|
||||
{
|
||||
var p1 = isVert
|
||||
? pnlPreview.PointWorldToGraph(sl.Position, cursor)
|
||||
: pnlPreview.PointWorldToGraph(cursor, sl.Position);
|
||||
var p2 = isVert
|
||||
? pnlPreview.PointWorldToGraph(sl.Position, gapStart)
|
||||
: pnlPreview.PointWorldToGraph(gapStart, sl.Position);
|
||||
g.DrawLine(splitPen, p1, p2);
|
||||
}
|
||||
cursor = fc + halfExt;
|
||||
}
|
||||
|
||||
// Final segment after last feature
|
||||
var end = extEnd + margin;
|
||||
if (end > cursor)
|
||||
{
|
||||
var p1 = isVert
|
||||
? pnlPreview.PointWorldToGraph(sl.Position, cursor)
|
||||
: pnlPreview.PointWorldToGraph(cursor, sl.Position);
|
||||
var p2 = isVert
|
||||
? pnlPreview.PointWorldToGraph(sl.Position, end)
|
||||
: pnlPreview.PointWorldToGraph(end, sl.Position);
|
||||
g.DrawLine(splitPen, p1, p2);
|
||||
}
|
||||
}
|
||||
g.DrawLine(splitPen, p1, p2);
|
||||
}
|
||||
|
||||
// Feature position handles
|
||||
@@ -509,6 +550,41 @@ public partial class SplitDrawingForm : Form
|
||||
lblStatus.Text = $"Part: {_drawingBounds.Width:F2} x {_drawingBounds.Length:F2} | {_splitLines.Count} split lines | {pieceCount} pieces";
|
||||
}
|
||||
|
||||
// --- Feature rendering helpers ---
|
||||
|
||||
private static ISplitFeature GetSplitFeature(SplitType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
SplitType.WeldGapTabs => new WeldGapTabSplit(),
|
||||
SplitType.SpikeGroove => new SpikeGrooveSplit(),
|
||||
_ => new StraightSplit()
|
||||
};
|
||||
}
|
||||
|
||||
private static double GetFeatureHalfExtent(SplitParameters p)
|
||||
{
|
||||
return p.Type switch
|
||||
{
|
||||
SplitType.WeldGapTabs => p.TabWidth / 2,
|
||||
SplitType.SpikeGroove => p.GrooveDepth * System.Math.Tan(OpenNest.Math.Angle.ToRadians(p.SpikeAngle / 2)),
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private void DrawFeatureEdge(Graphics g, Pen pen, List<Geometry.Entity> entities, bool isVertical)
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity is Geometry.Line line)
|
||||
{
|
||||
var p1 = pnlPreview.PointWorldToGraph(line.StartPoint.X, line.StartPoint.Y);
|
||||
var p2 = pnlPreview.PointWorldToGraph(line.EndPoint.X, line.EndPoint.Y);
|
||||
g.DrawLine(pen, p1, p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- SplitPreview control ---
|
||||
|
||||
private class SplitPreview : EntityView
|
||||
|
||||
Reference in New Issue
Block a user