Compare commits
13 Commits
134771aa23
...
5a9a06a6a0
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a9a06a6a0 | |||
| c1f1c829dc | |||
| e8fe01aea2 | |||
| 7b7d2cd8d1 | |||
| 6ca0e9da92 | |||
| bcaa4a03ee | |||
| 54c6f1bc89 | |||
| 429e4b63e1 | |||
| 159b54a1ec | |||
| 568539d5b1 | |||
| d7fa4bef43 | |||
| 7c58cfa749 | |||
| 525cbc6f12 |
@@ -1,4 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
@@ -59,9 +60,18 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
result.Codes.AddRange(leadIn.Generate(closestPt, normal, winding));
|
||||
var reindexed = cutout.ReindexAt(closestPt, entity);
|
||||
result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt));
|
||||
// TODO: MicrotabLeadOut — trim last cutting move by GapSize
|
||||
result.Codes.AddRange(leadOut.Generate(closestPt, normal, winding));
|
||||
|
||||
if (Parameters.TabsEnabled && Parameters.TabConfig != null)
|
||||
{
|
||||
var trimmed = TrimShapeForTab(reindexed, closestPt, Parameters.TabConfig.Size);
|
||||
result.Codes.AddRange(ConvertShapeToMoves(trimmed, closestPt));
|
||||
result.Codes.AddRange(leadOut.Generate(closestPt, normal, winding));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt));
|
||||
result.Codes.AddRange(leadOut.Generate(closestPt, normal, winding));
|
||||
}
|
||||
|
||||
currentPoint = closestPt;
|
||||
}
|
||||
@@ -80,9 +90,18 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
result.Codes.AddRange(leadIn.Generate(perimeterPt, normal, winding));
|
||||
var reindexed = profile.Perimeter.ReindexAt(perimeterPt, perimeterEntity);
|
||||
result.Codes.AddRange(ConvertShapeToMoves(reindexed, perimeterPt));
|
||||
// TODO: MicrotabLeadOut — trim last cutting move by GapSize
|
||||
result.Codes.AddRange(leadOut.Generate(perimeterPt, normal, winding));
|
||||
|
||||
if (Parameters.TabsEnabled && Parameters.TabConfig != null)
|
||||
{
|
||||
var trimmed = TrimShapeForTab(reindexed, perimeterPt, Parameters.TabConfig.Size);
|
||||
result.Codes.AddRange(ConvertShapeToMoves(trimmed, perimeterPt));
|
||||
result.Codes.AddRange(leadOut.Generate(perimeterPt, normal, winding));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Codes.AddRange(ConvertShapeToMoves(reindexed, perimeterPt));
|
||||
result.Codes.AddRange(leadOut.Generate(perimeterPt, normal, winding));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to incremental mode to match the convention used by
|
||||
@@ -150,6 +169,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
// Radial direction from center to point
|
||||
normal = point.AngleFrom(arc.Center);
|
||||
|
||||
// For CCW arcs the radial points the wrong way — flip it.
|
||||
// CW arcs are convex features (corners) where radial = outward.
|
||||
// CCW arcs are concave features (slots) where radial = inward.
|
||||
if (arc.Rotation == RotationType.CCW)
|
||||
normal += System.Math.PI;
|
||||
}
|
||||
else if (entity is Circle circle)
|
||||
{
|
||||
@@ -238,6 +263,70 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
};
|
||||
}
|
||||
|
||||
private static Shape TrimShapeForTab(Shape shape, Vector center, double tabSize)
|
||||
{
|
||||
var tabCircle = new Circle(center, tabSize);
|
||||
var entities = new List<Entity>(shape.Entities);
|
||||
|
||||
// Trim end: walk backward removing entities inside the tab circle
|
||||
while (entities.Count > 0)
|
||||
{
|
||||
var entity = entities[entities.Count - 1];
|
||||
if (entity.Intersects(tabCircle, out var pts) && pts.Count > 0)
|
||||
{
|
||||
// Find intersection furthest from center (furthest along path from end)
|
||||
var best = pts[0];
|
||||
var bestDist = best.DistanceTo(center);
|
||||
for (var j = 1; j < pts.Count; j++)
|
||||
{
|
||||
var dist = pts[j].DistanceTo(center);
|
||||
if (dist > bestDist)
|
||||
{
|
||||
best = pts[j];
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
if (entity is Line line)
|
||||
{
|
||||
var (first, _) = line.SplitAt(best);
|
||||
entities.RemoveAt(entities.Count - 1);
|
||||
if (first != null)
|
||||
entities.Add(first);
|
||||
}
|
||||
else if (entity is Arc arc)
|
||||
{
|
||||
var (first, _) = arc.SplitAt(best);
|
||||
entities.RemoveAt(entities.Count - 1);
|
||||
if (first != null)
|
||||
entities.Add(first);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// No intersection — entity is entirely inside circle, remove it
|
||||
if (EntityStartPoint(entity).DistanceTo(center) <= tabSize + Tolerance.Epsilon)
|
||||
{
|
||||
entities.RemoveAt(entities.Count - 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var result = new Shape();
|
||||
result.Entities.AddRange(entities);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Vector EntityStartPoint(Entity entity)
|
||||
{
|
||||
if (entity is Line line) return line.StartPoint;
|
||||
if (entity is Arc arc) return arc.StartPoint();
|
||||
return Vector.Zero;
|
||||
}
|
||||
|
||||
private List<ICode> ConvertShapeToMoves(Shape shape, Vector startPoint, LayerType layer = LayerType.Display)
|
||||
{
|
||||
var moves = new List<ICode>();
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle)
|
||||
{
|
||||
var approachAngle = contourNormalAngle + Angle.HalfPI - 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.HalfPI - 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));
|
||||
@@ -31,7 +31,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle)
|
||||
{
|
||||
var secondAngle = contourNormalAngle + Angle.HalfPI - 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);
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IConfigurablePostProcessor : IPostProcessor
|
||||
{
|
||||
object Config { get; }
|
||||
|
||||
void SaveConfig();
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,12 @@ public sealed class FeatureContext
|
||||
public string LibraryFile { get; set; } = "";
|
||||
public double CutDistance { get; set; }
|
||||
public double SheetDiagonal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Part location on the plate. Added to all output X/Y coordinates
|
||||
/// so part-relative programs become plate-absolute under G90.
|
||||
/// </summary>
|
||||
public Vector PartLocation { get; set; } = Vector.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -51,12 +57,13 @@ public sealed class CincinnatiFeatureWriter
|
||||
var currentPos = Vector.Zero;
|
||||
var lastFeedVar = "";
|
||||
var kerfEmitted = false;
|
||||
var offset = ctx.PartLocation;
|
||||
|
||||
// Find the pierce point from the first rapid move
|
||||
var piercePoint = FindPiercePoint(ctx.Codes);
|
||||
|
||||
// 1. Rapid to pierce point (with line number if configured)
|
||||
WriteRapidToPierce(writer, ctx.FeatureNumber, piercePoint);
|
||||
WriteRapidToPierce(writer, ctx.FeatureNumber, piercePoint, offset);
|
||||
|
||||
// 2. Part name comment on first feature of each part
|
||||
if (ctx.IsFirstFeatureOfPart && !string.IsNullOrEmpty(ctx.PartName))
|
||||
@@ -105,7 +112,7 @@ public sealed class CincinnatiFeatureWriter
|
||||
kerfEmitted = true;
|
||||
}
|
||||
|
||||
sb.Append($"G1 X{_fmt.FormatCoord(linear.EndPoint.X)} Y{_fmt.FormatCoord(linear.EndPoint.Y)}");
|
||||
sb.Append($"G1 X{_fmt.FormatCoord(linear.EndPoint.X + offset.X)} Y{_fmt.FormatCoord(linear.EndPoint.Y + offset.Y)}");
|
||||
|
||||
// Feedrate — etch always uses process feedrate
|
||||
var feedVar = ctx.IsEtch ? "#148" : GetLinearFeedVariable(linear.Layer);
|
||||
@@ -131,7 +138,7 @@ public sealed class CincinnatiFeatureWriter
|
||||
|
||||
// G2 = CW, G3 = CCW
|
||||
var gCode = arc.Rotation == RotationType.CW ? "G2" : "G3";
|
||||
sb.Append($"{gCode} X{_fmt.FormatCoord(arc.EndPoint.X)} Y{_fmt.FormatCoord(arc.EndPoint.Y)}");
|
||||
sb.Append($"{gCode} X{_fmt.FormatCoord(arc.EndPoint.X + offset.X)} Y{_fmt.FormatCoord(arc.EndPoint.Y + offset.Y)}");
|
||||
|
||||
// Convert absolute center to incremental I/J
|
||||
var i = arc.CenterPoint.X - currentPos.X;
|
||||
@@ -188,14 +195,14 @@ public sealed class CincinnatiFeatureWriter
|
||||
return Vector.Zero;
|
||||
}
|
||||
|
||||
private void WriteRapidToPierce(TextWriter writer, int featureNumber, Vector piercePoint)
|
||||
private void WriteRapidToPierce(TextWriter writer, int featureNumber, Vector piercePoint, Vector offset)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (_config.UseLineNumbers)
|
||||
sb.Append($"N{featureNumber} ");
|
||||
|
||||
sb.Append($"G0 X{_fmt.FormatCoord(piercePoint.X)} Y{_fmt.FormatCoord(piercePoint.Y)}");
|
||||
sb.Append($"G0 X{_fmt.FormatCoord(piercePoint.X + offset.X)} Y{_fmt.FormatCoord(piercePoint.Y + offset.Y)}");
|
||||
|
||||
writer.WriteLine(sb.ToString());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace OpenNest.Posts.Cincinnati
|
||||
{
|
||||
@@ -92,205 +93,159 @@ namespace OpenNest.Posts.Cincinnati
|
||||
/// </summary>
|
||||
public sealed class CincinnatiPostConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration name/identifier.
|
||||
/// Default: "CL940"
|
||||
/// </summary>
|
||||
[Category("1. Output")]
|
||||
[DisplayName("Configuration Name")]
|
||||
[Description("Configuration name/identifier (e.g. CL940).")]
|
||||
public string ConfigurationName { get; set; } = "CL940";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the units for posted output.
|
||||
/// Default: Units.Inches
|
||||
/// </summary>
|
||||
[Category("1. Output")]
|
||||
[DisplayName("Posted Units")]
|
||||
[Description("Units for posted output (Inches or Millimeters).")]
|
||||
public Units PostedUnits { get; set; } = Units.Inches;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the decimal accuracy for numeric output.
|
||||
/// Default: 4
|
||||
/// </summary>
|
||||
[Category("1. Output")]
|
||||
[DisplayName("Decimal Accuracy")]
|
||||
[Description("Number of decimal places for numeric output.")]
|
||||
public int PostedAccuracy { get; set; } = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how coordinate positioning is handled between parts.
|
||||
/// Default: CoordinateMode.G92
|
||||
/// </summary>
|
||||
public CoordinateMode CoordModeBetweenParts { get; set; } = CoordinateMode.G92;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use subprograms for sheet operations.
|
||||
/// Default: true
|
||||
/// </summary>
|
||||
public bool UseSheetSubprograms { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the starting subprogram number for sheet operations.
|
||||
/// Default: 101
|
||||
/// </summary>
|
||||
public int SheetSubprogramStart { get; set; } = 101;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use M98 sub-programs for part geometry.
|
||||
/// When enabled, each unique part geometry is written as a reusable sub-program
|
||||
/// called via M98, reducing output size for nests with repeated parts.
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
public bool UsePartSubprograms { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the starting sub-program number for part geometry sub-programs.
|
||||
/// Default: 200
|
||||
/// </summary>
|
||||
public int PartSubprogramStart { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subprogram number for variable declarations.
|
||||
/// Default: 100
|
||||
/// </summary>
|
||||
public int VariableDeclarationSubprogram { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how G89 parameters are provided.
|
||||
/// Default: G89Mode.LibraryFile
|
||||
/// </summary>
|
||||
public G89Mode ProcessParameterMode { get; set; } = G89Mode.LibraryFile;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default assist gas when Nest.AssistGas is empty.
|
||||
/// Default: "O2"
|
||||
/// </summary>
|
||||
public string DefaultAssistGas { get; set; } = "O2";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gas used for etch operations.
|
||||
/// Independent of the cutting assist gas — etch typically requires a specific gas.
|
||||
/// Default: "N2"
|
||||
/// </summary>
|
||||
public string DefaultEtchGas { get; set; } = "N2";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the material-to-library mapping for cut operations.
|
||||
/// Each entry maps (material, thickness, gas) to a G89 library file.
|
||||
/// </summary>
|
||||
public List<MaterialLibraryEntry> MaterialLibraries { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gas-to-library mapping for etch operations.
|
||||
/// Each entry maps a gas type to a G89 etch library file.
|
||||
/// </summary>
|
||||
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use exact stop mode (G61).
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
public bool UseExactStopMode { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets where kerf compensation is applied.
|
||||
/// Default: KerfMode.ControllerSide
|
||||
/// </summary>
|
||||
public KerfMode KerfCompensation { get; set; } = KerfMode.ControllerSide;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default side for kerf compensation.
|
||||
/// Default: KerfSide.Left
|
||||
/// </summary>
|
||||
public KerfSide DefaultKerfSide { get; set; } = KerfSide.Left;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how M47 is used in interior cuts.
|
||||
/// Default: M47Mode.Always
|
||||
/// </summary>
|
||||
public M47Mode InteriorM47 { get; set; } = M47Mode.Always;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how M47 is used in exterior cuts.
|
||||
/// Default: M47Mode.Always
|
||||
/// </summary>
|
||||
public M47Mode ExteriorM47 { get; set; } = M47Mode.Always;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the safety head raise distance (in machine units).
|
||||
/// Default: 2000
|
||||
/// </summary>
|
||||
public int? SafetyHeadraiseDistance { get; set; } = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the distance threshold for M47 override.
|
||||
/// Default: null
|
||||
/// </summary>
|
||||
public double? M47OverrideDistanceThreshold { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use anti-dive functionality.
|
||||
/// Default: true
|
||||
/// </summary>
|
||||
public bool UseAntiDive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use smart rapids optimization.
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
public bool UseSmartRapids { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets when pallet exchange occurs.
|
||||
/// Default: PalletMode.EndOfSheet
|
||||
/// </summary>
|
||||
public PalletMode PalletExchange { get; set; } = PalletMode.EndOfSheet;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use line numbers in output.
|
||||
/// Default: true
|
||||
/// </summary>
|
||||
[Category("1. Output")]
|
||||
[DisplayName("Use Line Numbers")]
|
||||
[Description("Include line numbers in output.")]
|
||||
public bool UseLineNumbers { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the starting line number for features.
|
||||
/// Default: 1
|
||||
/// </summary>
|
||||
[Category("1. Output")]
|
||||
[DisplayName("Feature Line Number Start")]
|
||||
[Description("Starting line number for features.")]
|
||||
public int FeatureLineNumberStart { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use speed/gas commands.
|
||||
/// Default: false
|
||||
/// </summary>
|
||||
[Category("2. Subprograms")]
|
||||
[DisplayName("Use Sheet Subprograms")]
|
||||
[Description("Use subprograms for sheet operations.")]
|
||||
public bool UseSheetSubprograms { get; set; } = true;
|
||||
|
||||
[Category("2. Subprograms")]
|
||||
[DisplayName("Sheet Subprogram Start")]
|
||||
[Description("Starting subprogram number for sheet operations.")]
|
||||
public int SheetSubprogramStart { get; set; } = 101;
|
||||
|
||||
[Category("2. Subprograms")]
|
||||
[DisplayName("Use Part Subprograms")]
|
||||
[Description("Use M98 sub-programs for part geometry. Reduces output size for repeated parts.")]
|
||||
public bool UsePartSubprograms { get; set; } = false;
|
||||
|
||||
[Category("2. Subprograms")]
|
||||
[DisplayName("Part Subprogram Start")]
|
||||
[Description("Starting sub-program number for part geometry sub-programs.")]
|
||||
public int PartSubprogramStart { get; set; } = 200;
|
||||
|
||||
[Category("2. Subprograms")]
|
||||
[DisplayName("Variable Declaration Subprogram")]
|
||||
[Description("Subprogram number for variable declarations.")]
|
||||
public int VariableDeclarationSubprogram { get; set; } = 100;
|
||||
|
||||
[Category("3. Positioning")]
|
||||
[DisplayName("Coordinate Mode Between Parts")]
|
||||
[Description("How coordinate positioning is handled between parts (G92, G91, or G53).")]
|
||||
public CoordinateMode CoordModeBetweenParts { get; set; } = CoordinateMode.G92;
|
||||
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Process Parameter Mode")]
|
||||
[Description("How G89 parameters are provided (LibraryFile or Explicit).")]
|
||||
public G89Mode ProcessParameterMode { get; set; } = G89Mode.LibraryFile;
|
||||
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Default Assist Gas")]
|
||||
[Description("Default assist gas when Nest.AssistGas is empty.")]
|
||||
public string DefaultAssistGas { get; set; } = "O2";
|
||||
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Default Etch Gas")]
|
||||
[Description("Gas used for etch operations.")]
|
||||
public string DefaultEtchGas { get; set; } = "N2";
|
||||
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Use Exact Stop Mode")]
|
||||
[Description("Enable exact stop mode (G61).")]
|
||||
public bool UseExactStopMode { get; set; } = false;
|
||||
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Use Speed/Gas Commands")]
|
||||
[Description("Enable speed/gas commands in output.")]
|
||||
public bool UseSpeedGas { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the feedrate percentage for lead-in moves.
|
||||
/// Default: 0.5 (50%)
|
||||
/// </summary>
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Use Anti-Dive")]
|
||||
[Description("Enable anti-dive functionality.")]
|
||||
public bool UseAntiDive { get; set; } = true;
|
||||
|
||||
[Category("4. Process")]
|
||||
[DisplayName("Use Smart Rapids")]
|
||||
[Description("Enable smart rapids optimization.")]
|
||||
public bool UseSmartRapids { get; set; } = false;
|
||||
|
||||
[Category("5. Kerf")]
|
||||
[DisplayName("Kerf Compensation")]
|
||||
[Description("Where kerf compensation is applied (ControllerSide or PreApplied).")]
|
||||
public KerfMode KerfCompensation { get; set; } = KerfMode.ControllerSide;
|
||||
|
||||
[Category("5. Kerf")]
|
||||
[DisplayName("Default Kerf Side")]
|
||||
[Description("Default side for kerf compensation (Left or Right).")]
|
||||
public KerfSide DefaultKerfSide { get; set; } = KerfSide.Left;
|
||||
|
||||
[Category("6. M47 (Optional Stop)")]
|
||||
[DisplayName("Interior M47")]
|
||||
[Description("How M47 is used in interior cuts.")]
|
||||
public M47Mode InteriorM47 { get; set; } = M47Mode.Always;
|
||||
|
||||
[Category("6. M47 (Optional Stop)")]
|
||||
[DisplayName("Exterior M47")]
|
||||
[Description("How M47 is used in exterior cuts.")]
|
||||
public M47Mode ExteriorM47 { get; set; } = M47Mode.Always;
|
||||
|
||||
[Category("6. M47 (Optional Stop)")]
|
||||
[DisplayName("M47 Override Distance Threshold")]
|
||||
[Description("Distance threshold for M47 override. Null = no override.")]
|
||||
public double? M47OverrideDistanceThreshold { get; set; } = null;
|
||||
|
||||
[Category("7. Safety")]
|
||||
[DisplayName("Safety Headraise Distance")]
|
||||
[Description("Safety head raise distance in machine units. Null = disabled.")]
|
||||
public int? SafetyHeadraiseDistance { get; set; } = 2000;
|
||||
|
||||
[Category("8. Pallet")]
|
||||
[DisplayName("Pallet Exchange")]
|
||||
[Description("When pallet exchange occurs (None, EndOfSheet, or StartAndEnd).")]
|
||||
public PalletMode PalletExchange { get; set; } = PalletMode.EndOfSheet;
|
||||
|
||||
[Category("9. Feedrates")]
|
||||
[DisplayName("Lead-In Feedrate %")]
|
||||
[Description("Feedrate percentage for lead-in moves (e.g. 0.5 = 50%).")]
|
||||
public double LeadInFeedratePercent { get; set; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the feedrate percentage for lead-in arc-to-line moves.
|
||||
/// Default: 0.5 (50%)
|
||||
/// </summary>
|
||||
[Category("9. Feedrates")]
|
||||
[DisplayName("Lead-In Arc Line 2 Feedrate %")]
|
||||
[Description("Feedrate percentage for lead-in arc-to-line moves.")]
|
||||
public double LeadInArcLine2FeedratePercent { get; set; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the feedrate percentage for lead-out moves.
|
||||
/// Default: 0.5 (50%)
|
||||
/// </summary>
|
||||
[Category("9. Feedrates")]
|
||||
[DisplayName("Lead-Out Feedrate %")]
|
||||
[Description("Feedrate percentage for lead-out moves.")]
|
||||
public double LeadOutFeedratePercent { get; set; } = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the feedrate multiplier for circular cuts.
|
||||
/// Default: 0.8 (80%)
|
||||
/// </summary>
|
||||
[Category("9. Feedrates")]
|
||||
[DisplayName("Circle Feedrate Multiplier")]
|
||||
[Description("Feedrate multiplier for circular cuts (e.g. 0.8 = 80%).")]
|
||||
public double CircleFeedrateMultiplier { get; set; } = 0.8;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the arc feedrate calculation mode.
|
||||
/// Default: ArcFeedrateMode.None
|
||||
/// </summary>
|
||||
[Category("9. Feedrates")]
|
||||
[DisplayName("Arc Feedrate Mode")]
|
||||
[Description("Arc feedrate calculation mode (None, Percentages, or Variables).")]
|
||||
public ArcFeedrateMode ArcFeedrate { get; set; } = ArcFeedrateMode.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the radius-based arc feedrate ranges.
|
||||
/// Ranges are matched from smallest MaxRadius to largest.
|
||||
/// </summary>
|
||||
[Category("9. Feedrates")]
|
||||
[DisplayName("Arc Feedrate Ranges")]
|
||||
[Description("Radius-based arc feedrate ranges. Matched from smallest to largest MaxRadius.")]
|
||||
public List<ArcFeedrateRange> ArcFeedrateRanges { get; set; } = new()
|
||||
{
|
||||
new() { MaxRadius = 0.125, FeedratePercent = 0.25, VariableNumber = 123 },
|
||||
@@ -298,17 +253,25 @@ namespace OpenNest.Posts.Cincinnati
|
||||
new() { MaxRadius = 4.500, FeedratePercent = 0.80, VariableNumber = 125 }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the variable number for sheet width.
|
||||
/// Default: 110
|
||||
/// </summary>
|
||||
[Category("A. Variables")]
|
||||
[DisplayName("Sheet Width Variable")]
|
||||
[Description("Variable number for sheet width.")]
|
||||
public int SheetWidthVariable { get; set; } = 110;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the variable number for sheet length.
|
||||
/// Default: 111
|
||||
/// </summary>
|
||||
[Category("A. Variables")]
|
||||
[DisplayName("Sheet Length Variable")]
|
||||
[Description("Variable number for sheet length.")]
|
||||
public int SheetLengthVariable { get; set; } = 111;
|
||||
|
||||
[Category("B. Libraries")]
|
||||
[DisplayName("Material Libraries")]
|
||||
[Description("Material-to-library mapping for cut operations. Maps (material, thickness, gas) to a G89 library file.")]
|
||||
public List<MaterialLibraryEntry> MaterialLibraries { get; set; } = new();
|
||||
|
||||
[Category("B. Libraries")]
|
||||
[DisplayName("Etch Libraries")]
|
||||
[Description("Gas-to-library mapping for etch operations.")]
|
||||
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MaterialLibraryEntry
|
||||
|
||||
@@ -9,7 +9,7 @@ using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest.Posts.Cincinnati
|
||||
{
|
||||
public sealed class CincinnatiPostProcessor : IPostProcessor
|
||||
public sealed class CincinnatiPostProcessor : IConfigurablePostProcessor
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -23,6 +23,8 @@ namespace OpenNest.Posts.Cincinnati
|
||||
|
||||
public CincinnatiPostConfig Config { get; }
|
||||
|
||||
object IConfigurablePostProcessor.Config => Config;
|
||||
|
||||
public CincinnatiPostProcessor()
|
||||
{
|
||||
var configPath = GetConfigPath();
|
||||
|
||||
@@ -153,7 +153,8 @@ public sealed class CincinnatiSheetWriter
|
||||
IsEtch = isEtch,
|
||||
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||
CutDistance = cutDistance,
|
||||
SheetDiagonal = sheetDiagonal
|
||||
SheetDiagonal = sheetDiagonal,
|
||||
PartLocation = part.Location
|
||||
};
|
||||
|
||||
_featureWriter.Write(w, ctx);
|
||||
@@ -240,7 +241,8 @@ public sealed class CincinnatiSheetWriter
|
||||
IsEtch = isEtch,
|
||||
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||
CutDistance = cutDistance,
|
||||
SheetDiagonal = sheetDiagonal
|
||||
SheetDiagonal = sheetDiagonal,
|
||||
PartLocation = part.Location
|
||||
};
|
||||
|
||||
_featureWriter.Write(w, ctx);
|
||||
|
||||
@@ -121,6 +121,17 @@ public class CincinnatiPostProcessorTests
|
||||
Assert.Equal("OpenNest", pp.Author);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Post_ImplementsIConfigurablePostProcessor()
|
||||
{
|
||||
var post = new CincinnatiPostProcessor(new CincinnatiPostConfig());
|
||||
var configurable = post as IConfigurablePostProcessor;
|
||||
|
||||
Assert.NotNull(configurable);
|
||||
Assert.NotNull(configurable.Config);
|
||||
Assert.IsType<CincinnatiPostConfig>(configurable.Config);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Post_SkipsEmptyPlates()
|
||||
{
|
||||
|
||||
@@ -280,6 +280,67 @@ public class CincinnatiSheetWriterTests
|
||||
Assert.False(FeatureUtils.IsEtch(codes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSheet_InlineCoordinates_AreAbsoluteOnPlate()
|
||||
{
|
||||
var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
|
||||
// Part program is at origin: (0,0) to (2,0) to (2,2) to (0,2) to (0,0)
|
||||
var pgm = new Program();
|
||||
pgm.Codes.Add(new RapidMove(0, 0));
|
||||
pgm.Codes.Add(new LinearMove(2, 0));
|
||||
pgm.Codes.Add(new LinearMove(2, 2));
|
||||
pgm.Codes.Add(new LinearMove(0, 2));
|
||||
pgm.Codes.Add(new LinearMove(0, 0));
|
||||
|
||||
var plate = new Plate(48.0, 96.0);
|
||||
// Place part at (10.5, 5.25) on the plate to produce non-integer coordinates
|
||||
plate.Parts.Add(new Part(new Drawing("Square", pgm), new Vector(10.5, 5.25)));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
using var sw = new StringWriter(sb);
|
||||
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||
|
||||
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "", "");
|
||||
|
||||
var output = sb.ToString();
|
||||
// Under G90, coordinates must be plate-absolute (part coords + part location)
|
||||
Assert.Contains("G0 X10.5 Y5.25", output); // rapid to pierce
|
||||
Assert.Contains("G1 X12.5 Y5.25", output); // (2,0) + (10.5,5.25)
|
||||
Assert.Contains("G1 X12.5 Y7.25", output); // (2,2) + (10.5,5.25)
|
||||
Assert.Contains("G1 X10.5 Y7.25", output); // (0,2) + (10.5,5.25)
|
||||
Assert.Contains("G1 X10.5 Y5.25", output); // (0,0) + (10.5,5.25)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteSheet_TwoPartsAtDifferentLocations_HaveDistinctAbsoluteCoords()
|
||||
{
|
||||
var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
|
||||
var pgm = new Program();
|
||||
pgm.Codes.Add(new RapidMove(0, 0));
|
||||
pgm.Codes.Add(new LinearMove(1, 0));
|
||||
pgm.Codes.Add(new LinearMove(1, 1));
|
||||
pgm.Codes.Add(new LinearMove(0, 0));
|
||||
|
||||
var drawing = new Drawing("Tri", pgm);
|
||||
var plate = new Plate(48.0, 96.0);
|
||||
plate.Parts.Add(new Part(drawing, new Vector(5.5, 3.25)));
|
||||
plate.Parts.Add(new Part(drawing, new Vector(20.5, 10.25)));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
using var sw = new StringWriter(sb);
|
||||
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||
|
||||
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "", "");
|
||||
|
||||
var output = sb.ToString();
|
||||
// First part at (5.5, 3.25)
|
||||
Assert.Contains("G0 X5.5 Y3.25", output);
|
||||
Assert.Contains("G1 X6.5 Y3.25", output);
|
||||
// Second part at (20.5, 10.25)
|
||||
Assert.Contains("G0 X20.5 Y10.25", output);
|
||||
Assert.Contains("G1 X21.5 Y10.25", output);
|
||||
}
|
||||
|
||||
private static Program CreateSimpleProgram()
|
||||
{
|
||||
var pgm = new Program();
|
||||
|
||||
@@ -23,7 +23,10 @@ namespace OpenNest.Actions
|
||||
private ContourType snapContourType;
|
||||
private double snapNormal;
|
||||
private bool hasSnap;
|
||||
private ShapeInfo hoveredContour;
|
||||
private ContextMenuStrip contextMenu;
|
||||
private static readonly Brush grayOverlay = new SolidBrush(Color.FromArgb(160, 180, 180, 180));
|
||||
private static readonly Pen highlightPen = new Pen(Color.Cyan, 2.5f);
|
||||
|
||||
public ActionLeadIn(PlateView plateView)
|
||||
: base(plateView)
|
||||
@@ -54,6 +57,7 @@ namespace OpenNest.Actions
|
||||
profile = null;
|
||||
contours = null;
|
||||
hasSnap = false;
|
||||
hoveredContour = null;
|
||||
plateView.Invalidate();
|
||||
}
|
||||
|
||||
@@ -77,6 +81,7 @@ namespace OpenNest.Actions
|
||||
// Find closest contour and point
|
||||
var bestDist = double.MaxValue;
|
||||
hasSnap = false;
|
||||
hoveredContour = null;
|
||||
|
||||
foreach (var info in contours)
|
||||
{
|
||||
@@ -91,6 +96,7 @@ namespace OpenNest.Actions
|
||||
snapContourType = info.ContourType;
|
||||
snapNormal = ContourCuttingStrategy.ComputeNormal(closest, entity, info.ContourType);
|
||||
hasSnap = true;
|
||||
hoveredContour = info;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +140,35 @@ namespace OpenNest.Actions
|
||||
|
||||
private void OnPaint(object sender, PaintEventArgs e)
|
||||
{
|
||||
var g = e.Graphics;
|
||||
|
||||
// Gray overlay on all parts except the selected one
|
||||
foreach (var lp in plateView.LayoutParts)
|
||||
{
|
||||
if (lp == selectedLayoutPart)
|
||||
continue;
|
||||
|
||||
if (lp.Path != null)
|
||||
g.FillPath(grayOverlay, lp.Path);
|
||||
}
|
||||
|
||||
// Highlight the hovered contour
|
||||
if (hoveredContour != null && selectedPart != null)
|
||||
{
|
||||
using var contourPath = hoveredContour.Shape.GetGraphicsPath();
|
||||
|
||||
// Translate from local part space to world space, then apply view transform
|
||||
using var contourMatrix = new Matrix();
|
||||
contourMatrix.Translate((float)selectedPart.Location.X, (float)selectedPart.Location.Y);
|
||||
contourMatrix.Multiply(plateView.Matrix, MatrixOrder.Append);
|
||||
contourPath.Transform(contourMatrix);
|
||||
|
||||
var prevSmooth = g.SmoothingMode;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.DrawPath(highlightPen, contourPath);
|
||||
g.SmoothingMode = prevSmooth;
|
||||
}
|
||||
|
||||
if (!hasSnap || selectedPart == null)
|
||||
return;
|
||||
|
||||
@@ -182,7 +217,6 @@ namespace OpenNest.Actions
|
||||
var piercePoint = leadIn.GetPiercePoint(snapPoint, snapNormal);
|
||||
var worldPierce = TransformToWorld(piercePoint);
|
||||
|
||||
var g = e.Graphics;
|
||||
var oldSmooth = g.SmoothingMode;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
|
||||
@@ -190,12 +224,12 @@ namespace OpenNest.Actions
|
||||
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);
|
||||
using var previewPen = new Pen(Color.Magenta, 2.0f / plateView.ViewScale);
|
||||
g.DrawLine(previewPen, 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);
|
||||
g.FillEllipse(Brushes.Magenta, 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);
|
||||
@@ -215,10 +249,6 @@ namespace OpenNest.Actions
|
||||
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;
|
||||
|
||||
@@ -312,6 +342,7 @@ namespace OpenNest.Actions
|
||||
profile = null;
|
||||
contours = null;
|
||||
hasSnap = false;
|
||||
hoveredContour = null;
|
||||
plateView.Invalidate();
|
||||
}
|
||||
|
||||
|
||||
@@ -515,23 +515,22 @@ namespace OpenNest.Controls
|
||||
|
||||
private void DrawAllCutDirectionArrows(Graphics g)
|
||||
{
|
||||
using var pen = new Pen(Color.FromArgb(220, Color.DarkCyan), 1.5f);
|
||||
using var brush = new SolidBrush(Color.FromArgb(220, Color.DarkCyan));
|
||||
using var pen = new Pen(Color.FromArgb(220, Color.Black), 1.5f);
|
||||
|
||||
var arrowSpacingWorld = view.LengthGuiToWorld(60f);
|
||||
var arrowSize = 5f;
|
||||
var arrowSize = 6f;
|
||||
|
||||
for (var i = 0; i < view.Plate.Parts.Count; ++i)
|
||||
{
|
||||
var part = view.Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
var pos = part.Location;
|
||||
DrawProgramCutDirectionArrows(g, pgm, ref pos, pen, brush, arrowSpacingWorld, arrowSize);
|
||||
DrawProgramCutDirectionArrows(g, pgm, ref pos, pen, arrowSpacingWorld, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProgramCutDirectionArrows(Graphics g, Program pgm, ref Vector pos,
|
||||
Pen pen, Brush brush, double spacing, float arrowSize)
|
||||
Pen pen, double spacing, float arrowSize)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
@@ -541,7 +540,7 @@ namespace OpenNest.Controls
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program != null)
|
||||
DrawProgramCutDirectionArrows(g, subpgm.Program, ref pos, pen, brush, spacing, arrowSize);
|
||||
DrawProgramCutDirectionArrows(g, subpgm.Program, ref pos, pen, spacing, arrowSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -555,7 +554,7 @@ namespace OpenNest.Controls
|
||||
{
|
||||
var line = (LinearMove)code;
|
||||
if (!line.Suppressed)
|
||||
DrawLineDirectionArrows(g, pos, endpt, brush, spacing, arrowSize);
|
||||
DrawLineDirectionArrows(g, pos, endpt, pen, spacing, arrowSize);
|
||||
}
|
||||
else if (code.Type == CodeType.ArcMove)
|
||||
{
|
||||
@@ -565,7 +564,7 @@ namespace OpenNest.Controls
|
||||
var center = pgm.Mode == Mode.Incremental
|
||||
? arc.CenterPoint + pos
|
||||
: arc.CenterPoint;
|
||||
DrawArcDirectionArrows(g, pos, endpt, center, arc.Rotation, brush, spacing, arrowSize);
|
||||
DrawArcDirectionArrows(g, pos, endpt, center, arc.Rotation, pen, spacing, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -574,7 +573,7 @@ namespace OpenNest.Controls
|
||||
}
|
||||
|
||||
private void DrawLineDirectionArrows(Graphics g, Vector start, Vector end,
|
||||
Brush brush, double spacing, float arrowSize)
|
||||
Pen pen, double spacing, float arrowSize)
|
||||
{
|
||||
var dx = end.X - start.X;
|
||||
var dy = end.Y - start.Y;
|
||||
@@ -593,12 +592,12 @@ namespace OpenNest.Controls
|
||||
var pt = new Vector(start.X + dirX * t, start.Y + dirY * t);
|
||||
var screenPt = view.PointWorldToGraph(pt);
|
||||
var angle = System.Math.Atan2(-dirY, dirX);
|
||||
DrawArrowHead(g, brush, screenPt, angle, arrowSize);
|
||||
DrawArrowHead(g, pen, screenPt, angle, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawArcDirectionArrows(Graphics g, Vector start, Vector end, Vector center,
|
||||
RotationType rotation, Brush brush, double spacing, float arrowSize)
|
||||
RotationType rotation, Pen pen, double spacing, float arrowSize)
|
||||
{
|
||||
var radius = center.DistanceTo(start);
|
||||
if (radius < Tolerance.Epsilon) return;
|
||||
@@ -644,11 +643,11 @@ namespace OpenNest.Controls
|
||||
tangent = angle - System.Math.PI / 2;
|
||||
|
||||
var screenAngle = System.Math.Atan2(-System.Math.Sin(tangent), System.Math.Cos(tangent));
|
||||
DrawArrowHead(g, brush, screenPt, screenAngle, arrowSize);
|
||||
DrawArrowHead(g, pen, screenPt, screenAngle, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawArrowHead(Graphics g, Brush brush, PointF tip, double angle, float size)
|
||||
private static void DrawArrowHead(Graphics g, Pen pen, PointF tip, double angle, float size)
|
||||
{
|
||||
var sin = (float)System.Math.Sin(angle);
|
||||
var cos = (float)System.Math.Cos(angle);
|
||||
@@ -658,14 +657,11 @@ namespace OpenNest.Controls
|
||||
var wingX = size * 0.5f * sin;
|
||||
var wingY = -size * 0.5f * cos;
|
||||
|
||||
var points = new PointF[]
|
||||
{
|
||||
tip,
|
||||
new PointF(tip.X + backX + wingX, tip.Y + backY + wingY),
|
||||
new PointF(tip.X + backX - wingX, tip.Y + backY - wingY),
|
||||
};
|
||||
var wing1 = new PointF(tip.X + backX + wingX, tip.Y + backY + wingY);
|
||||
var wing2 = new PointF(tip.X + backX - wingX, tip.Y + backY - wingY);
|
||||
|
||||
g.FillPolygon(brush, points);
|
||||
g.DrawLine(pen, wing1, tip);
|
||||
g.DrawLine(pen, wing2, tip);
|
||||
}
|
||||
|
||||
private void DrawLine(Graphics g, Vector pt1, Vector pt2, Pen pen)
|
||||
|
||||
@@ -1040,6 +1040,13 @@ namespace OpenNest.Forms
|
||||
if (postProcessor == null)
|
||||
return;
|
||||
|
||||
if (postProcessor is IConfigurablePostProcessor configurable)
|
||||
{
|
||||
using var configForm = new PostProcessorConfigForm(configurable);
|
||||
if (configForm.ShowDialog() != DialogResult.OK)
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new SaveFileDialog();
|
||||
dialog.Filter = "CNC File (*.cnc) | *.cnc";
|
||||
dialog.FileName = activeForm.Nest.Name;
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
partial class PostProcessorConfigForm
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.propertyGrid = new System.Windows.Forms.PropertyGrid();
|
||||
this.bottomPanel = new OpenNest.Controls.BottomPanel();
|
||||
this.okButton = new System.Windows.Forms.Button();
|
||||
this.cancelButton = new System.Windows.Forms.Button();
|
||||
this.bottomPanel.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// propertyGrid
|
||||
//
|
||||
this.propertyGrid.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.propertyGrid.Location = new System.Drawing.Point(0, 0);
|
||||
this.propertyGrid.Name = "propertyGrid";
|
||||
this.propertyGrid.Size = new System.Drawing.Size(484, 511);
|
||||
this.propertyGrid.TabIndex = 0;
|
||||
this.propertyGrid.ToolbarVisible = true;
|
||||
this.propertyGrid.PropertySort = System.Windows.Forms.PropertySort.Categorized;
|
||||
//
|
||||
// bottomPanel
|
||||
//
|
||||
this.bottomPanel.Controls.Add(this.okButton);
|
||||
this.bottomPanel.Controls.Add(this.cancelButton);
|
||||
this.bottomPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.bottomPanel.Location = new System.Drawing.Point(0, 511);
|
||||
this.bottomPanel.Name = "bottomPanel";
|
||||
this.bottomPanel.Size = new System.Drawing.Size(484, 50);
|
||||
this.bottomPanel.TabIndex = 1;
|
||||
//
|
||||
// okButton
|
||||
//
|
||||
this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
this.okButton.Location = new System.Drawing.Point(286, 11);
|
||||
this.okButton.Name = "okButton";
|
||||
this.okButton.Size = new System.Drawing.Size(90, 28);
|
||||
this.okButton.TabIndex = 0;
|
||||
this.okButton.Text = "OK";
|
||||
this.okButton.UseVisualStyleBackColor = true;
|
||||
this.okButton.Click += new System.EventHandler(this.okButton_Click);
|
||||
//
|
||||
// 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(382, 11);
|
||||
this.cancelButton.Name = "cancelButton";
|
||||
this.cancelButton.Size = new System.Drawing.Size(90, 28);
|
||||
this.cancelButton.TabIndex = 1;
|
||||
this.cancelButton.Text = "Cancel";
|
||||
this.cancelButton.UseVisualStyleBackColor = true;
|
||||
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
|
||||
//
|
||||
// PostProcessorConfigForm
|
||||
//
|
||||
this.AcceptButton = this.okButton;
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.ClientSize = new System.Drawing.Size(484, 561);
|
||||
this.Controls.Add(this.propertyGrid);
|
||||
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.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "PostProcessorConfigForm";
|
||||
this.ShowIcon = false;
|
||||
this.ShowInTaskbar = false;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Post Processor Settings";
|
||||
this.bottomPanel.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.PropertyGrid propertyGrid;
|
||||
private Controls.BottomPanel bottomPanel;
|
||||
private System.Windows.Forms.Button okButton;
|
||||
private System.Windows.Forms.Button cancelButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
public partial class PostProcessorConfigForm : Form
|
||||
{
|
||||
private readonly IConfigurablePostProcessor postProcessor;
|
||||
private readonly string configBackup;
|
||||
|
||||
public PostProcessorConfigForm(IConfigurablePostProcessor postProcessor)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
this.postProcessor = postProcessor;
|
||||
this.Text = postProcessor.Name + " Settings";
|
||||
|
||||
// Deep-clone config as JSON backup for cancel/restore
|
||||
configBackup = JsonSerializer.Serialize(postProcessor.Config, postProcessor.Config.GetType());
|
||||
|
||||
propertyGrid.SelectedObject = postProcessor.Config;
|
||||
}
|
||||
|
||||
private void okButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
postProcessor.SaveConfig();
|
||||
}
|
||||
|
||||
private void cancelButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
// Restore config from backup
|
||||
var original = JsonSerializer.Deserialize(configBackup, postProcessor.Config.GetType());
|
||||
var properties = postProcessor.Config.GetType().GetProperties();
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
if (prop.CanWrite)
|
||||
prop.SetValue(postProcessor.Config, prop.GetValue(original));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user