Compare commits

...

12 Commits

Author SHA1 Message Date
aj d16ef36d34 feat: add lead-out parameters and tab toggle to CuttingParametersForm
Restructure the cutting parameters dialog with separate Lead-In and
Lead-Out GroupBoxes per tab, exposing editable length/angle/radius
fields for lead-outs (previously hardcoded). Add Tabs section with
enable checkbox and width control. Also fix lead-in/lead-out angle
calculations and convert cutting strategy output to incremental mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 15:08:18 -04:00
aj 5307c5c85a feat: add ActionLeadIn for manual lead-in placement on part contours
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:47:22 -04:00
aj 21321740d6 feat: add Assign Lead-ins button to EditNestForm toolbar
Adds a text-only toolbar button to the Plates tab that opens the
CuttingParametersForm, saves the chosen parameters on the plate, and
runs LeadInAssigner with LeftSideSequencer to auto-assign lead-ins.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:39:40 -04:00
aj 7f8c708d3f feat: add CuttingParametersForm dialog for lead-in/lead-out configuration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:36:41 -04:00
aj ab4f806820 feat: render lead-in/lead-out codes in yellow, skip suppressed codes
Add GetGraphicsPaths/AddProgramSplit to GraphicsHelper that builds separate
GraphicsPath objects for cut vs lead-in/lead-out codes, skipping suppressed
codes. Update LayoutPart to use split paths when HasManualLeadIns is set,
drawing lead-in geometry in yellow regardless of selection state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:32:43 -04:00
aj c9b5ee1918 feat: serialize HasManualLeadIns, LeadInsLocked, and :SUPPRESSED in nest files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:29:49 -04:00
aj f34dce95da feat: add LeadInAssigner for auto-assigning lead-ins to plate parts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:26:43 -04:00
aj a2a19938d3 feat: add CuttingParameters property to Plate
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:22:28 -04:00
aj c064c7647a feat: add ApplyLeadIns/RemoveLeadIns to Part with CuttingParameters storage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:21:09 -04:00
aj 8a712b9755 feat: set Layer = Leadout on all LeadOut subclass generated codes
Adds LayerType.Leadout to all LinearMove and ArcMove instances produced
by LineLeadOut and ArcLeadOut Generate() methods.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 13:16:11 -04:00
aj 82de512f44 feat: set Layer = Leadin on all LeadIn subclass generated codes
Adds LayerType.Leadin to all LinearMove and ArcMove instances produced
by LineLeadIn, ArcLeadIn, LineArcLeadIn, CleanHoleLeadIn, and
LineLineLeadIn Generate() methods, plus tests covering all subclasses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 13:16:06 -04:00
aj f903cbe18a feat: add Motion.Suppressed property to mark tab-gap codes
Adds Suppressed bool to the Motion base class so LinearMove, ArcMove,
and RapidMove can be flagged for skip during rendering and post-processing
when they fall within a tab gap. Clone() updated on all three subclasses
to preserve the flag. Covered by new MotionSuppressedTests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:13:00 -04:00
33 changed files with 1741 additions and 143 deletions
+2 -1
View File
@@ -65,7 +65,8 @@ namespace OpenNest.CNC
{
return new ArcMove(EndPoint, CenterPoint, Rotation)
{
Layer = Layer
Layer = Layer,
Suppressed = Suppressed
};
}
@@ -63,6 +63,10 @@ namespace OpenNest.CNC.CuttingStrategy
result.Codes.AddRange(leadOut.Generate(perimeterPt, normal, winding));
}
// Convert to incremental mode to match the convention used by
// the rest of the system (rendering, bounding box, drag, etc.).
result.Mode = Mode.Incremental;
return new CuttingResult
{
Program = result,
@@ -102,7 +106,7 @@ namespace OpenNest.CNC.CuttingStrategy
return ordered;
}
private ContourType DetectContourType(Shape cutout)
public static ContourType DetectContourType(Shape cutout)
{
if (cutout.Entities.Count == 1 && cutout.Entities[0] is Circle)
return ContourType.ArcCircle;
@@ -110,7 +114,7 @@ namespace OpenNest.CNC.CuttingStrategy
return ContourType.Internal;
}
private double ComputeNormal(Vector point, Entity entity, ContourType contourType)
public static double ComputeNormal(Vector point, Entity entity, ContourType contourType)
{
double normal;
@@ -141,7 +145,7 @@ namespace OpenNest.CNC.CuttingStrategy
return Math.Angle.NormalizeRad(normal);
}
private RotationType DetermineWinding(Shape shape)
public static RotationType DetermineWinding(Shape shape)
{
// Use signed area: positive = CCW, negative = CW
var area = shape.Area();
@@ -19,7 +19,7 @@ namespace OpenNest.CNC.CuttingStrategy
return new List<ICode>
{
new RapidMove(piercePoint),
new ArcMove(contourStartPoint, arcCenter, winding)
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin }
};
}
@@ -27,8 +27,8 @@ namespace OpenNest.CNC.CuttingStrategy
return new List<ICode>
{
new RapidMove(piercePoint),
new LinearMove(arcStart),
new ArcMove(contourStartPoint, arcCenter, winding)
new LinearMove(arcStart) { Layer = LayerType.Leadin },
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin }
};
}
@@ -27,8 +27,8 @@ namespace OpenNest.CNC.CuttingStrategy
return new List<ICode>
{
new RapidMove(piercePoint),
new LinearMove(arcStart),
new ArcMove(contourStartPoint, arcCenter, winding)
new LinearMove(arcStart) { Layer = LayerType.Leadin },
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin }
};
}
@@ -17,13 +17,13 @@ namespace OpenNest.CNC.CuttingStrategy
return new List<ICode>
{
new RapidMove(piercePoint),
new LinearMove(contourStartPoint)
new LinearMove(contourStartPoint) { Layer = LayerType.Leadin }
};
}
public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle)
{
var approachAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle);
var approachAngle = contourNormalAngle + Angle.HalfPI - Angle.ToRadians(ApproachAngle);
return new Vector(
contourStartPoint.X + Length * System.Math.Cos(approachAngle),
contourStartPoint.Y + Length * System.Math.Sin(approachAngle));
@@ -16,7 +16,7 @@ namespace OpenNest.CNC.CuttingStrategy
{
var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle);
var secondAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle1);
var secondAngle = contourNormalAngle + Angle.HalfPI - Angle.ToRadians(ApproachAngle1);
var midPoint = new Vector(
contourStartPoint.X + Length2 * System.Math.Cos(secondAngle),
contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle));
@@ -24,14 +24,14 @@ namespace OpenNest.CNC.CuttingStrategy
return new List<ICode>
{
new RapidMove(piercePoint),
new LinearMove(midPoint),
new LinearMove(contourStartPoint)
new LinearMove(midPoint) { Layer = LayerType.Leadin },
new LinearMove(contourStartPoint) { Layer = LayerType.Leadin }
};
}
public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle)
{
var secondAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle1);
var secondAngle = contourNormalAngle + Angle.HalfPI - Angle.ToRadians(ApproachAngle1);
var midX = contourStartPoint.X + Length2 * System.Math.Cos(secondAngle);
var midY = contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle);
@@ -20,7 +20,7 @@ namespace OpenNest.CNC.CuttingStrategy
return new List<ICode>
{
new ArcMove(endPoint, arcCenter, winding)
new ArcMove(endPoint, arcCenter, winding) { Layer = LayerType.Leadout }
};
}
}
@@ -12,14 +12,14 @@ namespace OpenNest.CNC.CuttingStrategy
public override List<ICode> Generate(Vector contourEndPoint, double contourNormalAngle,
RotationType winding = RotationType.CW)
{
var overcutAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle);
var overcutAngle = contourNormalAngle + Angle.HalfPI - Angle.ToRadians(ApproachAngle);
var endPoint = new Vector(
contourEndPoint.X + Length * System.Math.Cos(overcutAngle),
contourEndPoint.Y + Length * System.Math.Sin(overcutAngle));
return new List<ICode>
{
new LinearMove(endPoint)
new LinearMove(endPoint) { Layer = LayerType.Leadout }
};
}
}
+2 -1
View File
@@ -31,7 +31,8 @@ namespace OpenNest.CNC
{
return new LinearMove(EndPoint)
{
Layer = Layer
Layer = Layer,
Suppressed = Suppressed
};
}
+2
View File
@@ -12,6 +12,8 @@ namespace OpenNest.CNC
public int Feedrate { get; set; }
public bool Suppressed { get; set; }
protected Motion()
{
Feedrate = CNC.Feedrate.UseDefault;
+4 -1
View File
@@ -26,7 +26,10 @@ namespace OpenNest.CNC
public override ICode Clone()
{
return new RapidMove(EndPoint);
return new RapidMove(EndPoint)
{
Suppressed = Suppressed
};
}
public override string ToString()
+33
View File
@@ -22,6 +22,7 @@ namespace OpenNest
{
private Vector location;
private bool ownsProgram;
private double preLeadInRotation;
public readonly Drawing BaseDrawing;
@@ -56,6 +57,38 @@ namespace OpenNest
public bool HasManualLeadIns { get; set; }
public bool LeadInsLocked { get; set; }
public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; }
public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint)
{
preLeadInRotation = Rotation;
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters };
var result = strategy.Apply(Program, approachPoint);
Program = result.Program;
CuttingParameters = parameters;
HasManualLeadIns = true;
UpdateBounds();
}
public void RemoveLeadIns()
{
var rotation = preLeadInRotation;
var location = Location;
Program = BaseDrawing.Program.Clone() as Program;
ownsProgram = true;
if (!Math.Tolerance.IsEqualTo(rotation, 0))
Program.Rotate(rotation);
Location = location;
HasManualLeadIns = false;
LeadInsLocked = false;
CuttingParameters = null;
UpdateBounds();
}
/// <summary>
/// Gets the rotation of the part in radians.
/// </summary>
+2
View File
@@ -88,6 +88,8 @@ namespace OpenNest
/// </summary>
public Material Material { get; set; }
public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; }
/// <summary>
/// Material grain direction in radians. 0 = horizontal.
/// </summary>
+2 -2
View File
@@ -13,9 +13,9 @@ namespace OpenNest
public static readonly Layer Display = new Layer("DISPLAY") { Color = Color.Cyan };
public static readonly Layer Leadin = new Layer("LEADIN") { Color = Color.Yellow };
public static readonly Layer Leadin = new Layer("LEADIN") { Color = Color.Brown };
public static readonly Layer Leadout = new Layer("LEADOUT") { Color = Color.Yellow };
public static readonly Layer Leadout = new Layer("LEADOUT") { Color = Color.Brown };
public static readonly Layer Scribe = new Layer("SCRIBE") { Color = Color.Magenta };
}
+40
View File
@@ -0,0 +1,40 @@
using OpenNest.Engine.Sequencing;
using OpenNest.Geometry;
using System.Linq;
namespace OpenNest.Engine
{
public class LeadInAssigner
{
public IPartSequencer Sequencer { get; set; }
public void Assign(Plate plate)
{
var parameters = plate.CuttingParameters;
if (parameters == null)
return;
var sequenced = Sequencer.Sequence(plate.Parts.ToList(), plate);
var currentPoint = PlateHelper.GetExitPoint(plate);
foreach (var sp in sequenced)
{
var part = sp.Part;
if (part.LeadInsLocked)
{
currentPoint = part.Location;
continue;
}
if (part.HasManualLeadIns)
part.RemoveLeadIns();
var localApproach = currentPoint - part.Location;
part.ApplyLeadIns(parameters, localApproach);
currentPoint = part.Location;
}
}
}
}
+2
View File
@@ -74,6 +74,8 @@ namespace OpenNest.IO
public double X { get; init; }
public double Y { get; init; }
public double Rotation { get; init; }
public bool HasManualLeadIns { get; init; }
public bool LeadInsLocked { get; init; }
}
public record CutOffDto
+2
View File
@@ -214,6 +214,8 @@ namespace OpenNest.IO
var part = new Part(dwg);
part.Rotate(partDto.Rotation);
part.Offset(new Vector(partDto.X, partDto.Y));
part.HasManualLeadIns = partDto.HasManualLeadIns;
part.LeadInsLocked = partDto.LeadInsLocked;
plate.Parts.Add(part);
}
+9 -1
View File
@@ -173,7 +173,9 @@ namespace OpenNest.IO
DrawingId = match.Key,
X = part.Location.X,
Y = part.Location.Y,
Rotation = part.Rotation
Rotation = part.Rotation,
HasManualLeadIns = part.HasManualLeadIns,
LeadInsLocked = part.LeadInsLocked
});
}
@@ -336,6 +338,9 @@ namespace OpenNest.IO
if (arcMove.Layer != LayerType.Cut)
sb.Append(GetLayerString(arcMove.Layer));
if (arcMove.Suppressed)
sb.Append(":SUPPRESSED");
return sb.ToString();
}
@@ -357,6 +362,9 @@ namespace OpenNest.IO
if (linearMove.Layer != LayerType.Cut)
sb.Append(GetLayerString(linearMove.Layer));
if (linearMove.Suppressed)
sb.Append(":SUPPRESSED");
return sb.ToString();
}
+47 -30
View File
@@ -142,6 +142,7 @@ namespace OpenNest.IO
double x = 0;
double y = 0;
var layer = LayerType.Cut;
var suppressed = false;
while (section == CodeSection.Line)
{
@@ -164,25 +165,32 @@ namespace OpenNest.IO
case ':':
{
var value = code.Value.Trim().ToUpper();
var tags = code.Value.Trim().ToUpper().Split(':');
switch (value)
foreach (var tag in tags)
{
case "DISPLAY":
layer = LayerType.Display;
break;
switch (tag)
{
case "DISPLAY":
layer = LayerType.Display;
break;
case "LEADIN":
layer = LayerType.Leadin;
break;
case "LEADIN":
layer = LayerType.Leadin;
break;
case "LEADOUT":
layer = LayerType.Leadout;
break;
case "LEADOUT":
layer = LayerType.Leadout;
break;
case "SCRIBE":
layer = LayerType.Scribe;
break;
case "SCRIBE":
layer = LayerType.Scribe;
break;
case "SUPPRESSED":
suppressed = true;
break;
}
}
break;
}
@@ -195,7 +203,7 @@ namespace OpenNest.IO
if (isRapid)
program.Codes.Add(new RapidMove(x, y));
else
program.Codes.Add(new LinearMove(x, y) { Layer = layer });
program.Codes.Add(new LinearMove(x, y) { Layer = layer, Suppressed = suppressed });
}
private void ReadArc(RotationType rotation)
@@ -205,6 +213,7 @@ namespace OpenNest.IO
double i = 0;
double j = 0;
var layer = LayerType.Cut;
var suppressed = false;
while (section == CodeSection.Arc)
{
@@ -236,25 +245,32 @@ namespace OpenNest.IO
case ':':
{
var value = code.Value.Trim().ToUpper();
var tags = code.Value.Trim().ToUpper().Split(':');
switch (value)
foreach (var tag in tags)
{
case "DISPLAY":
layer = LayerType.Display;
break;
switch (tag)
{
case "DISPLAY":
layer = LayerType.Display;
break;
case "LEADIN":
layer = LayerType.Leadin;
break;
case "LEADIN":
layer = LayerType.Leadin;
break;
case "LEADOUT":
layer = LayerType.Leadout;
break;
case "LEADOUT":
layer = LayerType.Leadout;
break;
case "SCRIBE":
layer = LayerType.Scribe;
break;
case "SCRIBE":
layer = LayerType.Scribe;
break;
case "SUPPRESSED":
suppressed = true;
break;
}
}
break;
}
@@ -269,7 +285,8 @@ namespace OpenNest.IO
EndPoint = new Vector(x, y),
CenterPoint = new Vector(i, j),
Rotation = rotation,
Layer = layer
Layer = layer,
Suppressed = suppressed
});
}
+98
View File
@@ -0,0 +1,98 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Engine;
using OpenNest.Engine.Sequencing;
using OpenNest.Geometry;
namespace OpenNest.Tests;
public class LeadInAssignerTests
{
private static Part MakeSquarePartAt(double x, double y)
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
pgm.Codes.Add(new LinearMove(new Vector(0, 10)));
pgm.Codes.Add(new LinearMove(new Vector(10, 10)));
pgm.Codes.Add(new LinearMove(new Vector(10, 0)));
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
var drawing = new Drawing("test", pgm);
return new Part(drawing, new Vector(x, y));
}
[Fact]
public void Assign_SetsHasManualLeadInsOnAllParts()
{
var plate = new Plate(60, 120);
plate.Parts.Add(MakeSquarePartAt(10, 10));
plate.Parts.Add(MakeSquarePartAt(30, 30));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
Assert.All(plate.Parts, p => Assert.True(p.HasManualLeadIns));
}
[Fact]
public void Assign_SkipsLockedParts()
{
var plate = new Plate(60, 120);
var lockedPart = MakeSquarePartAt(10, 10);
lockedPart.LeadInsLocked = true;
lockedPart.HasManualLeadIns = true;
var originalProgram = lockedPart.Program;
plate.Parts.Add(lockedPart);
plate.Parts.Add(MakeSquarePartAt(30, 30));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
Assert.Same(originalProgram, lockedPart.Program);
}
[Fact]
public void Assign_RemovesExistingLeadInsBeforeReapply()
{
var plate = new Plate(60, 120);
plate.Parts.Add(MakeSquarePartAt(10, 10));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
var countAfterFirst = plate.Parts[0].Program.Codes.Count;
assigner.Assign(plate);
var countAfterSecond = plate.Parts[0].Program.Codes.Count;
Assert.Equal(countAfterFirst, countAfterSecond);
}
[Fact]
public void Assign_PartsContainLeadinLayerCodes()
{
var plate = new Plate(60, 120);
plate.Parts.Add(MakeSquarePartAt(10, 10));
plate.CuttingParameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
var assigner = new LeadInAssigner { Sequencer = new LeftSideSequencer() };
assigner.Assign(plate);
var hasLeadin = plate.Parts[0].Program.Codes.OfType<LinearMove>().Any(m => m.Layer == LayerType.Leadin);
Assert.True(hasLeadin);
}
}
+90
View File
@@ -0,0 +1,90 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Geometry;
namespace OpenNest.Tests;
public class LeadInLayerTagTests
{
private static readonly Vector Point = new(5, 5);
private const double Normal = 0.0;
[Fact]
public void LineLeadIn_SetsLeadinLayer()
{
var leadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 };
var codes = leadIn.Generate(Point, Normal);
var linear = codes.OfType<LinearMove>().Single();
Assert.Equal(LayerType.Leadin, linear.Layer);
}
[Fact]
public void ArcLeadIn_SetsLeadinLayer()
{
var leadIn = new ArcLeadIn { Radius = 0.25 };
var codes = leadIn.Generate(Point, Normal);
var arc = codes.OfType<ArcMove>().Single();
Assert.Equal(LayerType.Leadin, arc.Layer);
}
[Fact]
public void LineArcLeadIn_SetsLeadinLayerOnAllMoves()
{
var leadIn = new LineArcLeadIn { LineLength = 0.5, ArcRadius = 0.25, ApproachAngle = 135 };
var codes = leadIn.Generate(Point, Normal);
Assert.All(codes.OfType<LinearMove>(), m => Assert.Equal(LayerType.Leadin, m.Layer));
Assert.All(codes.OfType<ArcMove>(), m => Assert.Equal(LayerType.Leadin, m.Layer));
}
[Fact]
public void CleanHoleLeadIn_SetsLeadinLayerOnAllMoves()
{
var leadIn = new CleanHoleLeadIn { LineLength = 0.5, ArcRadius = 0.25, Kerf = 0.05 };
var codes = leadIn.Generate(Point, Normal);
Assert.All(codes.OfType<LinearMove>(), m => Assert.Equal(LayerType.Leadin, m.Layer));
Assert.All(codes.OfType<ArcMove>(), m => Assert.Equal(LayerType.Leadin, m.Layer));
}
[Fact]
public void LineLineLeadIn_SetsLeadinLayerOnAllMoves()
{
var leadIn = new LineLineLeadIn { Length1 = 0.5, Length2 = 0.3, ApproachAngle1 = 90, ApproachAngle2 = 90 };
var codes = leadIn.Generate(Point, Normal);
Assert.All(codes.OfType<LinearMove>(), m => Assert.Equal(LayerType.Leadin, m.Layer));
}
[Fact]
public void NoLeadIn_NoLinearOrArcMoves()
{
var leadIn = new NoLeadIn();
var codes = leadIn.Generate(Point, Normal);
Assert.Empty(codes.OfType<LinearMove>());
Assert.Empty(codes.OfType<ArcMove>());
}
[Fact]
public void LineLeadOut_SetsLeadoutLayer()
{
var leadOut = new LineLeadOut { Length = 0.5, ApproachAngle = 90 };
var codes = leadOut.Generate(Point, Normal);
var linear = codes.OfType<LinearMove>().Single();
Assert.Equal(LayerType.Leadout, linear.Layer);
}
[Fact]
public void ArcLeadOut_SetsLeadoutLayer()
{
var leadOut = new ArcLeadOut { Radius = 0.25 };
var codes = leadOut.Generate(Point, Normal);
var arc = codes.OfType<ArcMove>().Single();
Assert.Equal(LayerType.Leadout, arc.Layer);
}
[Fact]
public void NoLeadOut_ReturnsEmptyList()
{
var leadOut = new NoLeadOut();
var codes = leadOut.Generate(Point, Normal);
Assert.Empty(codes);
}
}
+45
View File
@@ -0,0 +1,45 @@
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.Tests;
public class MotionSuppressedTests
{
[Fact]
public void LinearMove_Suppressed_DefaultsFalse()
{
var move = new LinearMove(new Vector(1, 2));
Assert.False(move.Suppressed);
}
[Fact]
public void ArcMove_Suppressed_DefaultsFalse()
{
var move = new ArcMove(new Vector(1, 2), new Vector(0, 0));
Assert.False(move.Suppressed);
}
[Fact]
public void RapidMove_Suppressed_DefaultsFalse()
{
var move = new RapidMove(new Vector(1, 2));
Assert.False(move.Suppressed);
}
[Fact]
public void Suppressed_CanBeSet()
{
var move = new LinearMove(new Vector(1, 2));
move.Suppressed = true;
Assert.True(move.Suppressed);
}
[Fact]
public void Clone_PreservesSuppressed()
{
var move = new LinearMove(new Vector(1, 2));
move.Suppressed = true;
var clone = (LinearMove)move.Clone();
Assert.True(clone.Suppressed);
}
}
+127
View File
@@ -0,0 +1,127 @@
using OpenNest.CNC;
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Geometry;
namespace OpenNest.Tests;
public class PartLeadInTests
{
private static Part MakeSquarePart()
{
var pgm = new Program();
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
pgm.Codes.Add(new LinearMove(new Vector(0, 10)));
pgm.Codes.Add(new LinearMove(new Vector(10, 10)));
pgm.Codes.Add(new LinearMove(new Vector(10, 0)));
pgm.Codes.Add(new LinearMove(new Vector(0, 0)));
var drawing = new Drawing("test", pgm);
return new Part(drawing);
}
[Fact]
public void ApplyLeadIns_SetsHasManualLeadIns()
{
var part = MakeSquarePart();
var parameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 },
InternalLeadIn = new LineLeadIn { Length = 0.25, ApproachAngle = 90 }
};
part.ApplyLeadIns(parameters, new Vector(-5, -5));
Assert.True(part.HasManualLeadIns);
}
[Fact]
public void ApplyLeadIns_StoresCuttingParameters()
{
var part = MakeSquarePart();
var parameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 },
InternalLeadIn = new LineLeadIn { Length = 0.25, ApproachAngle = 90 }
};
part.ApplyLeadIns(parameters, new Vector(-5, -5));
Assert.Same(parameters, part.CuttingParameters);
}
[Fact]
public void ApplyLeadIns_ProgramContainsLeadinCodes()
{
var part = MakeSquarePart();
var parameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
part.ApplyLeadIns(parameters, new Vector(-5, -5));
var hasLeadin = part.Program.Codes.OfType<LinearMove>().Any(m => m.Layer == LayerType.Leadin);
Assert.True(hasLeadin);
}
[Fact]
public void RemoveLeadIns_RestoresOriginalProgram()
{
var part = MakeSquarePart();
var originalCodeCount = part.Program.Codes.Count;
var parameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
part.ApplyLeadIns(parameters, new Vector(-5, -5));
part.RemoveLeadIns();
Assert.False(part.HasManualLeadIns);
Assert.Null(part.CuttingParameters);
Assert.Equal(originalCodeCount, part.Program.Codes.Count);
}
[Fact]
public void RemoveLeadIns_PreservesRotation()
{
var part = MakeSquarePart();
part.Rotate(System.Math.PI / 4); // 45 degrees
var rotation = part.Rotation;
var parameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
part.ApplyLeadIns(parameters, new Vector(-5, -5));
part.RemoveLeadIns();
Assert.Equal(rotation, part.Rotation, 6);
}
[Fact]
public void RemoveLeadIns_PreservesLocation()
{
var part = MakeSquarePart();
part.Offset(20, 30);
var location = part.Location;
var parameters = new CuttingParameters
{
ExternalLeadIn = new LineLeadIn { Length = 0.5, ApproachAngle = 90 }
};
part.ApplyLeadIns(parameters, new Vector(-5, -5));
part.RemoveLeadIns();
Assert.Equal(location.X, part.Location.X, 6);
Assert.Equal(location.Y, part.Location.Y, 6);
}
[Fact]
public void LeadInsLocked_DefaultsFalse()
{
var part = MakeSquarePart();
Assert.False(part.LeadInsLocked);
}
}
+330
View File
@@ -0,0 +1,330 @@
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Controls;
using OpenNest.Converters;
using OpenNest.Geometry;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace OpenNest.Actions
{
[DisplayName("Place Lead-in")]
public class ActionLeadIn : Action
{
private LayoutPart selectedLayoutPart;
private Part selectedPart;
private ShapeProfile profile;
private List<ShapeInfo> contours;
private Vector snapPoint;
private Entity snapEntity;
private ContourType snapContourType;
private double snapNormal;
private bool hasSnap;
private ContextMenuStrip contextMenu;
public ActionLeadIn(PlateView plateView)
: base(plateView)
{
ConnectEvents();
}
public override void ConnectEvents()
{
plateView.MouseMove += OnMouseMove;
plateView.MouseDown += OnMouseDown;
plateView.KeyDown += OnKeyDown;
plateView.Paint += OnPaint;
}
public override void DisconnectEvents()
{
plateView.MouseMove -= OnMouseMove;
plateView.MouseDown -= OnMouseDown;
plateView.KeyDown -= OnKeyDown;
plateView.Paint -= OnPaint;
contextMenu?.Dispose();
contextMenu = null;
selectedLayoutPart = null;
selectedPart = null;
profile = null;
contours = null;
hasSnap = false;
plateView.Invalidate();
}
public override void CancelAction() { }
public override bool IsBusy() => selectedPart != null;
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (selectedPart == null || contours == null)
return;
var worldPt = plateView.CurrentPoint;
// Transform world point into program-local space by subtracting the
// part's location. The contour shapes are already in the program's
// rotated coordinate system, so no additional un-rotation is needed.
var localPt = new Vector(worldPt.X - selectedPart.Location.X,
worldPt.Y - selectedPart.Location.Y);
// Find closest contour and point
var bestDist = double.MaxValue;
hasSnap = false;
foreach (var info in contours)
{
var closest = info.Shape.ClosestPointTo(localPt, out var entity);
var dist = closest.DistanceTo(localPt);
if (dist < bestDist)
{
bestDist = dist;
snapPoint = closest;
snapEntity = entity;
snapContourType = info.ContourType;
snapNormal = ContourCuttingStrategy.ComputeNormal(closest, entity, info.ContourType);
hasSnap = true;
}
}
plateView.Invalidate();
}
private void OnMouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (selectedPart == null)
{
// First click: select a part
SelectPartAtCursor();
}
else if (hasSnap)
{
// Second click: commit lead-in at snap point
CommitLeadIn();
}
}
else if (e.Button == MouseButtons.Right)
{
if (selectedPart != null && selectedPart.HasManualLeadIns)
ShowContextMenu(e.Location);
else
DeselectPart();
}
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Escape)
{
if (selectedPart != null)
DeselectPart();
else
plateView.SetAction(typeof(ActionSelect));
}
}
private void OnPaint(object sender, PaintEventArgs e)
{
if (!hasSnap || selectedPart == null)
return;
var parameters = plateView.Plate?.CuttingParameters;
if (parameters == null)
return;
// Transform snap point from local part space to world space
var worldSnap = TransformToWorld(snapPoint);
// Get the appropriate lead-in for this contour type
var leadIn = SelectLeadIn(parameters, snapContourType);
if (leadIn == null)
return;
// Get the pierce point (in local space)
var piercePoint = leadIn.GetPiercePoint(snapPoint, snapNormal);
var worldPierce = TransformToWorld(piercePoint);
var g = e.Graphics;
var oldSmooth = g.SmoothingMode;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Draw the lead-in preview as a line from pierce point to contour point
var pt1 = plateView.PointWorldToGraph(worldPierce);
var pt2 = plateView.PointWorldToGraph(worldSnap);
using var pen = new Pen(Color.Yellow, 2.0f / plateView.ViewScale);
g.DrawLine(pen, pt1, pt2);
// Draw a small circle at the pierce point
var radius = 3.0f / plateView.ViewScale;
g.FillEllipse(Brushes.Yellow, pt1.X - radius, pt1.Y - radius, radius * 2, radius * 2);
// Draw a small circle at the contour start point
g.FillEllipse(Brushes.Lime, pt2.X - radius, pt2.Y - radius, radius * 2, radius * 2);
g.SmoothingMode = oldSmooth;
}
private void SelectPartAtCursor()
{
var layoutPart = plateView.GetPartAtPoint(plateView.CurrentPoint);
if (layoutPart == null)
return;
var part = layoutPart.BasePart;
// Don't allow lead-in placement on cut-off parts
if (part.BaseDrawing.IsCutOff)
return;
// If part already has locked lead-ins, don't allow re-placement
if (part.LeadInsLocked)
return;
selectedLayoutPart = layoutPart;
selectedPart = part;
// Build contour info from the part's program geometry
BuildContourInfo();
// Highlight the selected part
layoutPart.IsSelected = true;
plateView.Invalidate();
}
private void BuildContourInfo()
{
// Get a clean program (no lead-ins) in the part's current rotated space.
// If the part has manual lead-ins, rebuild from base drawing + rotation.
// Otherwise the current Program is already clean and rotated.
CNC.Program cleanProgram;
if (selectedPart.HasManualLeadIns)
{
cleanProgram = selectedPart.BaseDrawing.Program.Clone() as CNC.Program;
if (!OpenNest.Math.Tolerance.IsEqualTo(selectedPart.Rotation, 0))
cleanProgram.Rotate(selectedPart.Rotation);
}
else
{
cleanProgram = selectedPart.Program;
}
var entities = ConvertProgram.ToGeometry(cleanProgram);
profile = new ShapeProfile(entities);
contours = new List<ShapeInfo>();
// Perimeter is always External
if (profile.Perimeter != null)
{
contours.Add(new ShapeInfo
{
Shape = profile.Perimeter,
ContourType = ContourType.External
});
}
// Cutouts
foreach (var cutout in profile.Cutouts)
{
contours.Add(new ShapeInfo
{
Shape = cutout,
ContourType = ContourCuttingStrategy.DetectContourType(cutout)
});
}
}
private void CommitLeadIn()
{
var parameters = plateView.Plate?.CuttingParameters;
if (parameters == null)
return;
// Remove any existing lead-ins first
if (selectedPart.HasManualLeadIns)
selectedPart.RemoveLeadIns();
// Apply lead-ins using the snap point as the approach point.
// snapPoint is in the program's local coordinate space (rotated, not offset),
// which is what Part.ApplyLeadIns expects.
selectedPart.ApplyLeadIns(parameters, snapPoint);
selectedPart.LeadInsLocked = true;
// Rebuild the layout part's graphics
selectedLayoutPart.IsDirty = true;
selectedLayoutPart.Update();
// Deselect and reset
DeselectPart();
plateView.Invalidate();
}
private void DeselectPart()
{
if (selectedLayoutPart != null)
{
selectedLayoutPart.IsSelected = false;
selectedLayoutPart = null;
}
selectedPart = null;
profile = null;
contours = null;
hasSnap = false;
plateView.Invalidate();
}
private void ShowContextMenu(Point location)
{
contextMenu?.Dispose();
contextMenu = new ContextMenuStrip();
var removeItem = new ToolStripMenuItem("Remove All Lead-ins");
removeItem.Click += (s, e) =>
{
selectedPart.RemoveLeadIns();
selectedLayoutPart.IsDirty = true;
selectedLayoutPart.Update();
DeselectPart();
plateView.Invalidate();
};
contextMenu.Items.Add(removeItem);
contextMenu.Show(plateView, location);
}
private Vector TransformToWorld(Vector localPt)
{
// The contours are already in rotated local space (we rotated the program
// before building the profile), so just add the part location offset
return new Vector(localPt.X + selectedPart.Location.X,
localPt.Y + selectedPart.Location.Y);
}
private static LeadIn SelectLeadIn(CuttingParameters parameters, ContourType contourType)
{
return contourType switch
{
ContourType.ArcCircle => parameters.ArcCircleLeadIn ?? parameters.InternalLeadIn,
ContourType.Internal => parameters.InternalLeadIn,
_ => parameters.ExternalLeadIn
};
}
private class ShapeInfo
{
public Shape Shape { get; set; }
public ContourType ContourType { get; set; }
}
}
}
+156
View File
@@ -0,0 +1,156 @@
namespace OpenNest.Forms
{
partial class CuttingParametersForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tabControl = new System.Windows.Forms.TabControl();
this.tabExternal = new System.Windows.Forms.TabPage();
this.tabInternal = new System.Windows.Forms.TabPage();
this.tabArcCircle = new System.Windows.Forms.TabPage();
this.acceptButton = new System.Windows.Forms.Button();
this.cancelButton = new System.Windows.Forms.Button();
this.bottomPanel = new OpenNest.Controls.BottomPanel();
this.tabControl.SuspendLayout();
this.bottomPanel.SuspendLayout();
this.SuspendLayout();
//
// tabControl
//
this.tabControl.Controls.Add(this.tabExternal);
this.tabControl.Controls.Add(this.tabInternal);
this.tabControl.Controls.Add(this.tabArcCircle);
this.tabControl.Dock = System.Windows.Forms.DockStyle.Top;
this.tabControl.Location = new System.Drawing.Point(0, 0);
this.tabControl.Margin = new System.Windows.Forms.Padding(4);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new System.Drawing.Size(380, 348);
this.tabControl.TabIndex = 0;
//
// tabExternal
//
this.tabExternal.Location = new System.Drawing.Point(4, 25);
this.tabExternal.Margin = new System.Windows.Forms.Padding(4);
this.tabExternal.Name = "tabExternal";
this.tabExternal.Padding = new System.Windows.Forms.Padding(8);
this.tabExternal.Size = new System.Drawing.Size(372, 319);
this.tabExternal.TabIndex = 0;
this.tabExternal.Text = "External";
this.tabExternal.UseVisualStyleBackColor = true;
//
// tabInternal
//
this.tabInternal.Location = new System.Drawing.Point(4, 25);
this.tabInternal.Margin = new System.Windows.Forms.Padding(4);
this.tabInternal.Name = "tabInternal";
this.tabInternal.Padding = new System.Windows.Forms.Padding(8);
this.tabInternal.Size = new System.Drawing.Size(372, 319);
this.tabInternal.TabIndex = 1;
this.tabInternal.Text = "Internal";
this.tabInternal.UseVisualStyleBackColor = true;
//
// tabArcCircle
//
this.tabArcCircle.Location = new System.Drawing.Point(4, 25);
this.tabArcCircle.Margin = new System.Windows.Forms.Padding(4);
this.tabArcCircle.Name = "tabArcCircle";
this.tabArcCircle.Padding = new System.Windows.Forms.Padding(8);
this.tabArcCircle.Size = new System.Drawing.Size(372, 319);
this.tabArcCircle.TabIndex = 2;
this.tabArcCircle.Text = "Arc / Circle";
this.tabArcCircle.UseVisualStyleBackColor = true;
//
// acceptButton
//
this.acceptButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.acceptButton.DialogResult = System.Windows.Forms.DialogResult.OK;
this.acceptButton.Location = new System.Drawing.Point(165, 11);
this.acceptButton.Margin = new System.Windows.Forms.Padding(4);
this.acceptButton.Name = "acceptButton";
this.acceptButton.Size = new System.Drawing.Size(90, 28);
this.acceptButton.TabIndex = 8;
this.acceptButton.Text = "OK";
this.acceptButton.UseVisualStyleBackColor = true;
//
// cancelButton
//
this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cancelButton.Location = new System.Drawing.Point(263, 11);
this.cancelButton.Margin = new System.Windows.Forms.Padding(4);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(90, 28);
this.cancelButton.TabIndex = 9;
this.cancelButton.Text = "Cancel";
this.cancelButton.UseVisualStyleBackColor = true;
//
// bottomPanel
//
this.bottomPanel.Controls.Add(this.acceptButton);
this.bottomPanel.Controls.Add(this.cancelButton);
this.bottomPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.bottomPanel.Location = new System.Drawing.Point(0, 406);
this.bottomPanel.Name = "bottomPanel";
this.bottomPanel.Size = new System.Drawing.Size(380, 50);
this.bottomPanel.TabIndex = 1;
//
// CuttingParametersForm
//
this.AcceptButton = this.acceptButton;
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cancelButton;
this.ClientSize = new System.Drawing.Size(380, 456);
this.Controls.Add(this.tabControl);
this.Controls.Add(this.bottomPanel);
this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.Margin = new System.Windows.Forms.Padding(4);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "CuttingParametersForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Cutting Parameters";
this.tabControl.ResumeLayout(false);
this.bottomPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TabControl tabControl;
private System.Windows.Forms.TabPage tabExternal;
private System.Windows.Forms.TabPage tabInternal;
private System.Windows.Forms.TabPage tabArcCircle;
private Controls.BottomPanel bottomPanel;
private System.Windows.Forms.Button acceptButton;
private System.Windows.Forms.Button cancelButton;
}
}
+476
View File
@@ -0,0 +1,476 @@
using OpenNest.CNC.CuttingStrategy;
using System;
using System.Windows.Forms;
namespace OpenNest.Forms
{
public partial class CuttingParametersForm : Form
{
private static readonly string[] LeadInTypes =
{ "None", "Line", "Arc", "Line + Arc", "Clean Hole", "Line + Line" };
private static readonly string[] LeadOutTypes =
{ "None", "Line", "Arc", "Microtab" };
private ComboBox cboExternalLeadIn, cboExternalLeadOut;
private ComboBox cboInternalLeadIn, cboInternalLeadOut;
private ComboBox cboArcCircleLeadIn, cboArcCircleLeadOut;
private Panel pnlExternalLeadIn, pnlExternalLeadOut;
private Panel pnlInternalLeadIn, pnlInternalLeadOut;
private Panel pnlArcCircleLeadIn, pnlArcCircleLeadOut;
private CheckBox chkTabsEnabled;
private NumericUpDown nudTabWidth;
public CuttingParameters Parameters { get; set; } = new CuttingParameters();
public CuttingParametersForm()
{
InitializeComponent();
SetupTab(tabExternal,
out cboExternalLeadIn, out pnlExternalLeadIn,
out cboExternalLeadOut, out pnlExternalLeadOut);
SetupTab(tabInternal,
out cboInternalLeadIn, out pnlInternalLeadIn,
out cboInternalLeadOut, out pnlInternalLeadOut);
SetupTab(tabArcCircle,
out cboArcCircleLeadIn, out pnlArcCircleLeadIn,
out cboArcCircleLeadOut, out pnlArcCircleLeadOut);
SetupTabsSection();
PopulateDropdowns();
cboExternalLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
cboInternalLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
cboArcCircleLeadIn.SelectedIndexChanged += OnLeadInTypeChanged;
cboExternalLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
cboInternalLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
cboArcCircleLeadOut.SelectedIndexChanged += OnLeadOutTypeChanged;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
LoadFromParameters(Parameters);
}
private static void SetupTab(TabPage tab,
out ComboBox leadInCombo, out Panel leadInPanel,
out ComboBox leadOutCombo, out Panel leadOutPanel)
{
var grpLeadIn = new GroupBox
{
Text = "Lead-In",
Location = new System.Drawing.Point(4, 4),
Size = new System.Drawing.Size(364, 168)
};
tab.Controls.Add(grpLeadIn);
grpLeadIn.Controls.Add(new Label
{
Text = "Type:",
Location = new System.Drawing.Point(8, 22),
AutoSize = true
});
leadInCombo = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Location = new System.Drawing.Point(90, 19),
Size = new System.Drawing.Size(250, 24)
};
grpLeadIn.Controls.Add(leadInCombo);
leadInPanel = new Panel
{
Location = new System.Drawing.Point(8, 48),
Size = new System.Drawing.Size(340, 112),
AutoScroll = true
};
grpLeadIn.Controls.Add(leadInPanel);
var grpLeadOut = new GroupBox
{
Text = "Lead-Out",
Location = new System.Drawing.Point(4, 176),
Size = new System.Drawing.Size(364, 132)
};
tab.Controls.Add(grpLeadOut);
grpLeadOut.Controls.Add(new Label
{
Text = "Type:",
Location = new System.Drawing.Point(8, 22),
AutoSize = true
});
leadOutCombo = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Location = new System.Drawing.Point(90, 19),
Size = new System.Drawing.Size(250, 24)
};
grpLeadOut.Controls.Add(leadOutCombo);
leadOutPanel = new Panel
{
Location = new System.Drawing.Point(8, 48),
Size = new System.Drawing.Size(340, 76),
AutoScroll = true
};
grpLeadOut.Controls.Add(leadOutPanel);
}
private void SetupTabsSection()
{
var grpTabs = new GroupBox
{
Text = "Tabs",
Location = new System.Drawing.Point(4, 350),
Size = new System.Drawing.Size(372, 55),
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
};
chkTabsEnabled = new CheckBox
{
Text = "Enable Tabs",
Location = new System.Drawing.Point(12, 22),
AutoSize = true
};
chkTabsEnabled.CheckedChanged += (s, e) => nudTabWidth.Enabled = chkTabsEnabled.Checked;
grpTabs.Controls.Add(chkTabsEnabled);
grpTabs.Controls.Add(new Label
{
Text = "Width:",
Location = new System.Drawing.Point(160, 23),
AutoSize = true
});
nudTabWidth = new NumericUpDown
{
Location = new System.Drawing.Point(215, 20),
Size = new System.Drawing.Size(100, 22),
DecimalPlaces = 4,
Increment = 0.0625m,
Minimum = 0,
Maximum = 9999,
Value = 0.25m,
Enabled = false
};
grpTabs.Controls.Add(nudTabWidth);
Controls.Add(grpTabs);
}
private void PopulateDropdowns()
{
foreach (var combo in new[] { cboExternalLeadIn, cboInternalLeadIn, cboArcCircleLeadIn })
{
combo.Items.AddRange(LeadInTypes);
combo.SelectedIndex = 0;
}
foreach (var combo in new[] { cboExternalLeadOut, cboInternalLeadOut, cboArcCircleLeadOut })
{
combo.Items.AddRange(LeadOutTypes);
combo.SelectedIndex = 0;
}
}
private void OnLeadInTypeChanged(object sender, EventArgs e)
{
var combo = (ComboBox)sender;
var panel = GetLeadInPanel(combo);
if (panel != null)
BuildLeadInParamControls(panel, combo.SelectedIndex);
}
private void OnLeadOutTypeChanged(object sender, EventArgs e)
{
var combo = (ComboBox)sender;
var panel = GetLeadOutPanel(combo);
if (panel != null)
BuildLeadOutParamControls(panel, combo.SelectedIndex);
}
private Panel GetLeadInPanel(ComboBox combo)
{
if (combo == cboExternalLeadIn) return pnlExternalLeadIn;
if (combo == cboInternalLeadIn) return pnlInternalLeadIn;
if (combo == cboArcCircleLeadIn) return pnlArcCircleLeadIn;
return null;
}
private Panel GetLeadOutPanel(ComboBox combo)
{
if (combo == cboExternalLeadOut) return pnlExternalLeadOut;
if (combo == cboInternalLeadOut) return pnlInternalLeadOut;
if (combo == cboArcCircleLeadOut) return pnlArcCircleLeadOut;
return null;
}
private static void BuildLeadInParamControls(Panel panel, int typeIndex)
{
panel.Controls.Clear();
var y = 0;
switch (typeIndex)
{
case 1: // Line
AddNumericField(panel, "Length:", 0.25, ref y, "Length");
AddNumericField(panel, "Approach Angle:", 90, ref y, "ApproachAngle");
break;
case 2: // Arc
AddNumericField(panel, "Radius:", 0.25, ref y, "Radius");
break;
case 3: // Line + Arc
AddNumericField(panel, "Line Length:", 0.25, ref y, "LineLength");
AddNumericField(panel, "Arc Radius:", 0.125, ref y, "ArcRadius");
AddNumericField(panel, "Approach Angle:", 135, ref y, "ApproachAngle");
break;
case 4: // Clean Hole
AddNumericField(panel, "Line Length:", 0.25, ref y, "LineLength");
AddNumericField(panel, "Arc Radius:", 0.125, ref y, "ArcRadius");
AddNumericField(panel, "Kerf:", 0.06, ref y, "Kerf");
break;
case 5: // Line + Line
AddNumericField(panel, "Length 1:", 0.25, ref y, "Length1");
AddNumericField(panel, "Angle 1:", 90, ref y, "Angle1");
AddNumericField(panel, "Length 2:", 0.25, ref y, "Length2");
AddNumericField(panel, "Angle 2:", 90, ref y, "Angle2");
break;
}
}
private static void BuildLeadOutParamControls(Panel panel, int typeIndex)
{
panel.Controls.Clear();
var y = 0;
switch (typeIndex)
{
case 1: // Line
AddNumericField(panel, "Length:", 0.25, ref y, "Length");
AddNumericField(panel, "Approach Angle:", 90, ref y, "ApproachAngle");
break;
case 2: // Arc
AddNumericField(panel, "Radius:", 0.25, ref y, "Radius");
break;
case 3: // Microtab
AddNumericField(panel, "Gap Size:", 0.06, ref y, "GapSize");
break;
}
}
private static void AddNumericField(Panel panel, string label, double defaultValue,
ref int y, string tag)
{
panel.Controls.Add(new Label
{
Text = label,
Location = new System.Drawing.Point(0, y + 3),
AutoSize = true
});
panel.Controls.Add(new NumericUpDown
{
Location = new System.Drawing.Point(130, y),
Size = new System.Drawing.Size(120, 22),
DecimalPlaces = 4,
Increment = 0.0625m,
Minimum = 0,
Maximum = 9999,
Value = (decimal)defaultValue,
Tag = tag
});
y += 30;
}
private void LoadFromParameters(CuttingParameters p)
{
LoadLeadIn(cboExternalLeadIn, pnlExternalLeadIn, p.ExternalLeadIn);
LoadLeadOut(cboExternalLeadOut, pnlExternalLeadOut, p.ExternalLeadOut);
LoadLeadIn(cboInternalLeadIn, pnlInternalLeadIn, p.InternalLeadIn);
LoadLeadOut(cboInternalLeadOut, pnlInternalLeadOut, p.InternalLeadOut);
LoadLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn, p.ArcCircleLeadIn);
LoadLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut, p.ArcCircleLeadOut);
chkTabsEnabled.Checked = p.TabsEnabled;
if (p.TabConfig != null)
nudTabWidth.Value = (decimal)p.TabConfig.Size;
}
private static void LoadLeadIn(ComboBox combo, Panel panel, LeadIn leadIn)
{
switch (leadIn)
{
case LineLeadIn line:
combo.SelectedIndex = 1;
SetParam(panel, "Length", line.Length);
SetParam(panel, "ApproachAngle", line.ApproachAngle);
break;
case ArcLeadIn arc:
combo.SelectedIndex = 2;
SetParam(panel, "Radius", arc.Radius);
break;
case LineArcLeadIn lineArc:
combo.SelectedIndex = 3;
SetParam(panel, "LineLength", lineArc.LineLength);
SetParam(panel, "ArcRadius", lineArc.ArcRadius);
SetParam(panel, "ApproachAngle", lineArc.ApproachAngle);
break;
case CleanHoleLeadIn cleanHole:
combo.SelectedIndex = 4;
SetParam(panel, "LineLength", cleanHole.LineLength);
SetParam(panel, "ArcRadius", cleanHole.ArcRadius);
SetParam(panel, "Kerf", cleanHole.Kerf);
break;
case LineLineLeadIn lineLine:
combo.SelectedIndex = 5;
SetParam(panel, "Length1", lineLine.Length1);
SetParam(panel, "Angle1", lineLine.ApproachAngle1);
SetParam(panel, "Length2", lineLine.Length2);
SetParam(panel, "Angle2", lineLine.ApproachAngle2);
break;
default:
combo.SelectedIndex = 0;
break;
}
}
private static void LoadLeadOut(ComboBox combo, Panel panel, LeadOut leadOut)
{
switch (leadOut)
{
case LineLeadOut line:
combo.SelectedIndex = 1;
SetParam(panel, "Length", line.Length);
SetParam(panel, "ApproachAngle", line.ApproachAngle);
break;
case ArcLeadOut arc:
combo.SelectedIndex = 2;
SetParam(panel, "Radius", arc.Radius);
break;
case MicrotabLeadOut microtab:
combo.SelectedIndex = 3;
SetParam(panel, "GapSize", microtab.GapSize);
break;
default:
combo.SelectedIndex = 0;
break;
}
}
public CuttingParameters BuildParameters()
{
var p = new CuttingParameters
{
ExternalLeadIn = BuildLeadIn(cboExternalLeadIn, pnlExternalLeadIn),
ExternalLeadOut = BuildLeadOut(cboExternalLeadOut, pnlExternalLeadOut),
InternalLeadIn = BuildLeadIn(cboInternalLeadIn, pnlInternalLeadIn),
InternalLeadOut = BuildLeadOut(cboInternalLeadOut, pnlInternalLeadOut),
ArcCircleLeadIn = BuildLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn),
ArcCircleLeadOut = BuildLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut),
TabsEnabled = chkTabsEnabled.Checked,
TabConfig = new NormalTab { Size = (double)nudTabWidth.Value }
};
return p;
}
private static LeadIn BuildLeadIn(ComboBox combo, Panel panel)
{
switch (combo.SelectedIndex)
{
case 1:
return new LineLeadIn
{
Length = GetParam(panel, "Length", 0.25),
ApproachAngle = GetParam(panel, "ApproachAngle", 90)
};
case 2:
return new ArcLeadIn
{
Radius = GetParam(panel, "Radius", 0.25)
};
case 3:
return new LineArcLeadIn
{
LineLength = GetParam(panel, "LineLength", 0.25),
ArcRadius = GetParam(panel, "ArcRadius", 0.125),
ApproachAngle = GetParam(panel, "ApproachAngle", 135)
};
case 4:
return new CleanHoleLeadIn
{
LineLength = GetParam(panel, "LineLength", 0.25),
ArcRadius = GetParam(panel, "ArcRadius", 0.125),
Kerf = GetParam(panel, "Kerf", 0.06)
};
case 5:
return new LineLineLeadIn
{
Length1 = GetParam(panel, "Length1", 0.25),
ApproachAngle1 = GetParam(panel, "Angle1", 90),
Length2 = GetParam(panel, "Length2", 0.25),
ApproachAngle2 = GetParam(panel, "Angle2", 90)
};
default:
return new NoLeadIn();
}
}
private static LeadOut BuildLeadOut(ComboBox combo, Panel panel)
{
switch (combo.SelectedIndex)
{
case 1:
return new LineLeadOut
{
Length = GetParam(panel, "Length", 0.25),
ApproachAngle = GetParam(panel, "ApproachAngle", 90)
};
case 2:
return new ArcLeadOut
{
Radius = GetParam(panel, "Radius", 0.25)
};
case 3:
return new MicrotabLeadOut
{
GapSize = GetParam(panel, "GapSize", 0.06)
};
default:
return new NoLeadOut();
}
}
private static void SetParam(Panel panel, string tag, double value)
{
foreach (Control c in panel.Controls)
{
if (c is NumericUpDown nud && (string)nud.Tag == tag)
{
nud.Value = (decimal)value;
return;
}
}
}
private static double GetParam(Panel panel, string tag, double defaultValue)
{
foreach (Control c in panel.Controls)
{
if (c is NumericUpDown nud && (string)nud.Tag == tag)
return (double)nud.Value;
}
return defaultValue;
}
}
}
+26 -2
View File
@@ -38,6 +38,8 @@
this.qtyColumn = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.toolStripButton1 = new System.Windows.Forms.ToolStripButton();
this.btnAssignLeadIns = new System.Windows.Forms.ToolStripButton();
this.btnPlaceLeadIn = new System.Windows.Forms.ToolStripButton();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.drawingListBox1 = new OpenNest.Controls.DrawingListBox();
this.toolStrip2 = new System.Windows.Forms.ToolStrip();
@@ -133,7 +135,9 @@
this.toolStrip1.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
this.toolStrip1.ImageScalingSize = new System.Drawing.Size(20, 20);
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.toolStripButton1});
this.toolStripButton1,
this.btnAssignLeadIns,
this.btnPlaceLeadIn});
this.toolStrip1.Location = new System.Drawing.Point(3, 3);
this.toolStrip1.Name = "toolStrip1";
this.toolStrip1.Size = new System.Drawing.Size(227, 31);
@@ -151,7 +155,25 @@
this.toolStripButton1.Size = new System.Drawing.Size(38, 28);
this.toolStripButton1.Text = "Calculate Cut Time";
this.toolStripButton1.Click += new System.EventHandler(this.CalculateSelectedPlateCutTime_Click);
//
//
// btnAssignLeadIns
//
this.btnAssignLeadIns.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.btnAssignLeadIns.ImageTransparentColor = System.Drawing.Color.Magenta;
this.btnAssignLeadIns.Name = "btnAssignLeadIns";
this.btnAssignLeadIns.Size = new System.Drawing.Size(96, 28);
this.btnAssignLeadIns.Text = "Assign Lead-ins";
this.btnAssignLeadIns.Click += new System.EventHandler(this.AssignLeadIns_Click);
//
// btnPlaceLeadIn
//
this.btnPlaceLeadIn.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.btnPlaceLeadIn.ImageTransparentColor = System.Drawing.Color.Magenta;
this.btnPlaceLeadIn.Name = "btnPlaceLeadIn";
this.btnPlaceLeadIn.Size = new System.Drawing.Size(90, 28);
this.btnPlaceLeadIn.Text = "Place Lead-in";
this.btnPlaceLeadIn.Click += new System.EventHandler(this.PlaceLeadIn_Click);
//
// tabPage2
//
this.tabPage2.Controls.Add(this.drawingListBox1);
@@ -266,5 +288,7 @@
private System.Windows.Forms.ToolStripButton toolStripButton2;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
private System.Windows.Forms.ToolStripButton toolStripButton3;
private System.Windows.Forms.ToolStripButton btnAssignLeadIns;
private System.Windows.Forms.ToolStripButton btnPlaceLeadIn;
}
}
+47
View File
@@ -2,6 +2,7 @@
using OpenNest.CNC.CuttingStrategy;
using OpenNest.Collections;
using OpenNest.Controls;
using OpenNest.Engine;
using OpenNest.Engine.Sequencing;
using OpenNest.IO;
using OpenNest.Math;
@@ -711,6 +712,52 @@ namespace OpenNest.Forms
CalculateCurrentPlateCutTime();
}
private void AssignLeadIns_Click(object sender, EventArgs e)
{
if (PlateView?.Plate == null)
return;
var plate = PlateView.Plate;
using var form = new CuttingParametersForm();
if (plate.CuttingParameters != null)
form.Parameters = plate.CuttingParameters;
if (form.ShowDialog(this) != DialogResult.OK)
return;
var parameters = form.BuildParameters();
plate.CuttingParameters = parameters;
var assigner = new LeadInAssigner
{
Sequencer = new LeftSideSequencer()
};
assigner.Assign(plate);
PlateView.Invalidate();
}
private void PlaceLeadIn_Click(object sender, EventArgs e)
{
if (PlateView?.Plate == null)
return;
var plate = PlateView.Plate;
// Ensure cutting parameters are configured
if (plate.CuttingParameters == null)
{
using var form = new CuttingParametersForm();
if (form.ShowDialog(this) != DialogResult.OK)
return;
plate.CuttingParameters = form.BuildParameters();
}
PlateView.SetAction(typeof(Actions.ActionLeadIn));
}
private void ImportDrawings_Click(object sender, EventArgs e)
{
Import();
+57 -59
View File
@@ -28,77 +28,75 @@
/// </summary>
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.numericUpDown1 = new OpenNest.Controls.NumericUpDown();
this.tableLayoutPanel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.SuspendLayout();
tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
label1 = new System.Windows.Forms.Label();
numericUpDown1 = new OpenNest.Controls.NumericUpDown();
tableLayoutPanel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)numericUpDown1).BeginInit();
SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.numericUpDown1, 1, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(220, 36);
this.tableLayoutPanel1.TabIndex = 0;
tableLayoutPanel1.ColumnCount = 2;
tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
tableLayoutPanel1.Controls.Add(label1, 0, 0);
tableLayoutPanel1.Controls.Add(numericUpDown1, 1, 0);
tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
tableLayoutPanel1.Name = "tableLayoutPanel1";
tableLayoutPanel1.RowCount = 1;
tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
tableLayoutPanel1.Size = new System.Drawing.Size(266, 35);
tableLayoutPanel1.TabIndex = 0;
//
// label1
//
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(3, 11);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(62, 13);
this.label1.TabIndex = 0;
this.label1.Text = "Sequence :";
label1.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(4, 10);
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(64, 15);
label1.TabIndex = 0;
label1.Text = "Sequence :";
//
// numericUpDown1
//
this.numericUpDown1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
this.numericUpDown1.Location = new System.Drawing.Point(71, 8);
this.numericUpDown1.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.numericUpDown1.Name = "numericUpDown1";
this.numericUpDown1.Size = new System.Drawing.Size(146, 20);
this.numericUpDown1.TabIndex = 1;
this.numericUpDown1.Value = new decimal(new int[] {
1,
0,
0,
0});
this.numericUpDown1.Leave += new System.EventHandler(this.numericUpDown1_Leave);
numericUpDown1.Anchor = System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
numericUpDown1.Location = new System.Drawing.Point(76, 6);
numericUpDown1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
numericUpDown1.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
numericUpDown1.Name = "numericUpDown1";
numericUpDown1.Size = new System.Drawing.Size(186, 23);
numericUpDown1.Suffix = "";
numericUpDown1.TabIndex = 1;
numericUpDown1.Value = new decimal(new int[] { 1, 0, 0, 0 });
numericUpDown1.Leave += numericUpDown1_Leave;
//
// SequenceForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(220, 36);
this.ControlBox = false;
this.Controls.Add(this.tableLayoutPanel1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Location = new System.Drawing.Point(100, 100);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "SequenceForm";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
this.Text = "Set Sequence";
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
this.ResumeLayout(false);
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(266, 35);
ControlBox = false;
Controls.Add(tableLayoutPanel1);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
Location = new System.Drawing.Point(100, 100);
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
MaximizeBox = false;
MinimizeBox = false;
MinimumSize = new System.Drawing.Size(268, 74);
Name = "SequenceForm";
ShowIcon = false;
ShowInTaskbar = false;
StartPosition = System.Windows.Forms.FormStartPosition.Manual;
Text = "Set Sequence";
tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel1.PerformLayout();
((System.ComponentModel.ISupportInitialize)numericUpDown1).EndInit();
ResumeLayout(false);
}
+27 -27
View File
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
+68
View File
@@ -85,6 +85,74 @@ namespace OpenNest
return img;
}
public static void GetGraphicsPaths(this Program pgm, Vector origin,
out GraphicsPath cutPath, out GraphicsPath leadPath)
{
cutPath = new GraphicsPath();
leadPath = new GraphicsPath();
var curpos = origin;
AddProgramSplit(cutPath, leadPath, pgm, pgm.Mode, ref curpos);
}
private static void AddProgramSplit(GraphicsPath cutPath, GraphicsPath leadPath,
Program pgm, Mode mode, ref Vector curpos)
{
mode = pgm.Mode;
for (var i = 0; i < pgm.Length; ++i)
{
var code = pgm[i];
switch (code.Type)
{
case CodeType.ArcMove:
var arc = (ArcMove)code;
if (arc.Suppressed)
{
var endpt = arc.EndPoint;
if (mode == Mode.Incremental) endpt += curpos;
curpos = endpt;
break;
}
var arcPath = (arc.Layer == LayerType.Leadin || arc.Layer == LayerType.Leadout)
? leadPath : cutPath;
AddArc(arcPath, arc, mode, ref curpos);
break;
case CodeType.LinearMove:
var line = (LinearMove)code;
if (line.Suppressed)
{
var endpt = line.EndPoint;
if (mode == Mode.Incremental) endpt += curpos;
curpos = endpt;
break;
}
var linePath = (line.Layer == LayerType.Leadin || line.Layer == LayerType.Leadout)
? leadPath : cutPath;
AddLine(linePath, line, mode, ref curpos);
break;
case CodeType.RapidMove:
AddLine(cutPath, (RapidMove)code, mode, ref curpos);
break;
case CodeType.SubProgramCall:
var tmpmode = mode;
var subpgm = (SubProgramCall)code;
if (subpgm.Program != null)
{
cutPath.StartFigure();
leadPath.StartFigure();
AddProgramSplit(cutPath, leadPath, subpgm.Program, mode, ref curpos);
}
mode = tmpmode;
break;
}
}
}
private static void AddArc(GraphicsPath path, ArcMove arc, Mode mode, ref Vector curpos)
{
var endpt = arc.EndPoint;
+26 -2
View File
@@ -15,6 +15,7 @@ namespace OpenNest
private static Color selectedColor;
private static Pen selectedPen;
private static Brush selectedBrush;
private static Pen leadInPen;
private Color color;
private Brush brush;
@@ -34,6 +35,7 @@ namespace OpenNest
{
programIdFont = new Font(SystemFonts.DefaultFont, FontStyle.Bold | FontStyle.Underline);
SelectedColor = Color.FromArgb(90, 150, 200, 255);
leadInPen = new Pen(Color.OrangeRed, 1.5f);
}
private LayoutPart(Part part)
@@ -52,6 +54,8 @@ namespace OpenNest
public GraphicsPath Path { get; private set; }
public GraphicsPath LeadInPath { get; private set; }
public Color Color
{
get { return color; }
@@ -83,6 +87,9 @@ namespace OpenNest
g.FillPath(brush, Path);
g.DrawPath(pen, Path);
}
if (LeadInPath != null)
g.DrawPath(leadInPen, LeadInPath);
}
public void Draw(Graphics g, string id)
@@ -98,6 +105,9 @@ namespace OpenNest
g.DrawPath(pen, Path);
}
if (LeadInPath != null)
g.DrawPath(leadInPen, LeadInPath);
using var sf = new StringFormat
{
Alignment = StringAlignment.Center,
@@ -138,8 +148,22 @@ namespace OpenNest
public void Update(DrawControl plateView)
{
Path = GraphicsHelper.GetGraphicsPath(BasePart.Program, BasePart.Location);
Path.Transform(plateView.Matrix);
if (BasePart.HasManualLeadIns)
{
BasePart.Program.GetGraphicsPaths(BasePart.Location, out var cutPath, out var leadPath);
cutPath.Transform(plateView.Matrix);
leadPath.Transform(plateView.Matrix);
Path = cutPath;
LeadInPath?.Dispose();
LeadInPath = leadPath;
}
else
{
Path = GraphicsHelper.GetGraphicsPath(BasePart.Program, BasePart.Location);
Path.Transform(plateView.Matrix);
LeadInPath?.Dispose();
LeadInPath = null;
}
_labelPoint ??= ComputeLabelPoint();
var rotatedLabel = _labelPoint.Value.Rotate(BasePart.Rotation);