fix: add proper spacing between G-code words in Cincinnati post output
G-code output was concatenated without spaces (e.g. N1005G0X1.4375Y-0.6562). Now emits standard spacing (N1005 G0 X1.4375 Y-0.6562) across all motion commands, line numbers, kerf comp, feedrates, M-codes, and comments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,11 +30,14 @@ public sealed class CincinnatiSheetWriter
|
||||
/// <summary>
|
||||
/// Writes a complete sheet subprogram for the given plate.
|
||||
/// </summary>
|
||||
/// <param name="cutLibrary">Resolved G89 library file for cut operations.</param>
|
||||
/// <param name="etchLibrary">Resolved G89 library file for etch operations.</param>
|
||||
/// <param name="partSubprograms">
|
||||
/// Optional mapping of (drawingId, rotationKey) to sub-program number.
|
||||
/// When provided, non-cutoff parts are emitted as M98 calls instead of inline features.
|
||||
/// </param>
|
||||
public void Write(TextWriter w, Plate plate, string nestName, int sheetIndex, int subNumber,
|
||||
string cutLibrary, string etchLibrary,
|
||||
Dictionary<(int, long), int> partSubprograms = null)
|
||||
{
|
||||
if (plate.Parts.Count == 0)
|
||||
@@ -43,7 +46,6 @@ public sealed class CincinnatiSheetWriter
|
||||
var width = plate.Size.Width;
|
||||
var length = plate.Size.Length;
|
||||
var sheetDiagonal = System.Math.Sqrt(width * width + length * length);
|
||||
var libraryFile = _config.DefaultLibraryFile ?? "";
|
||||
var varDeclSub = _config.VariableDeclarationSubprogram;
|
||||
var partCount = plate.Parts.Count(p => !p.BaseDrawing.IsCutOff);
|
||||
|
||||
@@ -55,20 +57,20 @@ public sealed class CincinnatiSheetWriter
|
||||
w.WriteLine($"( Layout {sheetIndex} )");
|
||||
w.WriteLine($"( SHEET NAME = {_fmt.FormatCoord(length)} X {_fmt.FormatCoord(width)} )");
|
||||
w.WriteLine($"( Total parts on sheet = {partCount} )");
|
||||
w.WriteLine($"#{_config.SheetWidthVariable}={_fmt.FormatCoord(width)}(SHEET WIDTH FOR CUTOFFS)");
|
||||
w.WriteLine($"#{_config.SheetLengthVariable}={_fmt.FormatCoord(length)}(SHEET LENGTH FOR CUTOFFS)");
|
||||
w.WriteLine($"#{_config.SheetWidthVariable}={_fmt.FormatCoord(width)} (SHEET WIDTH FOR CUTOFFS)");
|
||||
w.WriteLine($"#{_config.SheetLengthVariable}={_fmt.FormatCoord(length)} (SHEET LENGTH FOR CUTOFFS)");
|
||||
|
||||
// 2. Coordinate setup
|
||||
w.WriteLine("M42");
|
||||
w.WriteLine("N10000");
|
||||
w.WriteLine("G92X#5021Y#5022");
|
||||
if (!string.IsNullOrEmpty(libraryFile))
|
||||
w.WriteLine($"G89 P {libraryFile}");
|
||||
w.WriteLine("G92 X#5021 Y#5022");
|
||||
if (!string.IsNullOrEmpty(cutLibrary))
|
||||
w.WriteLine($"G89 P {cutLibrary}");
|
||||
w.WriteLine($"M98 P{varDeclSub} (Variable Declaration)");
|
||||
w.WriteLine("G90");
|
||||
w.WriteLine("M47(CPT)");
|
||||
if (!string.IsNullOrEmpty(libraryFile))
|
||||
w.WriteLine($"G89 P {libraryFile}");
|
||||
w.WriteLine("M47");
|
||||
if (!string.IsNullOrEmpty(cutLibrary))
|
||||
w.WriteLine($"G89 P {cutLibrary}");
|
||||
w.WriteLine("GOTO1( Goto Feature )");
|
||||
|
||||
// 3. Order parts: non-cutoff sorted by Bottom then Left, cutoffs last
|
||||
@@ -86,20 +88,20 @@ public sealed class CincinnatiSheetWriter
|
||||
|
||||
// 4. Emit parts
|
||||
if (partSubprograms != null)
|
||||
WritePartsWithSubprograms(w, allParts, libraryFile, sheetDiagonal, partSubprograms);
|
||||
WritePartsWithSubprograms(w, allParts, cutLibrary, etchLibrary, sheetDiagonal, partSubprograms);
|
||||
else
|
||||
WritePartsInline(w, allParts, libraryFile, sheetDiagonal);
|
||||
WritePartsInline(w, allParts, cutLibrary, etchLibrary, sheetDiagonal);
|
||||
|
||||
// 5. Footer
|
||||
w.WriteLine("M42");
|
||||
w.WriteLine("G0X0Y0");
|
||||
w.WriteLine("G0 X0 Y0");
|
||||
if (_config.PalletExchange != PalletMode.None)
|
||||
w.WriteLine($"N{sheetIndex + 1}M50");
|
||||
w.WriteLine($"M99(END OF {nestName}.{sheetIndex:D3})");
|
||||
w.WriteLine($"N{sheetIndex + 1} M50");
|
||||
w.WriteLine($"M99 (END OF {nestName}.{sheetIndex:D3})");
|
||||
}
|
||||
|
||||
private void WritePartsWithSubprograms(TextWriter w, List<Part> allParts,
|
||||
string libraryFile, double sheetDiagonal,
|
||||
string cutLibrary, string etchLibrary, double sheetDiagonal,
|
||||
Dictionary<(int, long), int> partSubprograms)
|
||||
{
|
||||
var lastPartName = "";
|
||||
@@ -126,26 +128,28 @@ public sealed class CincinnatiSheetWriter
|
||||
else
|
||||
{
|
||||
// Inline features for cutoffs or parts without sub-programs
|
||||
var features = SplitPartFeatures(part);
|
||||
var features = SplitAndOrderFeatures(part);
|
||||
for (var f = 0; f < features.Count; f++)
|
||||
{
|
||||
var (codes, isEtch) = features[f];
|
||||
var featureNumber = featureIndex == 0
|
||||
? _config.FeatureLineNumberStart
|
||||
: 1000 + featureIndex + 1;
|
||||
|
||||
var isLastFeature = isLastPart && f == features.Count - 1;
|
||||
var cutDistance = ComputeCutDistance(features[f]);
|
||||
var cutDistance = ComputeCutDistance(codes);
|
||||
|
||||
var ctx = new FeatureContext
|
||||
{
|
||||
Codes = features[f],
|
||||
Codes = codes,
|
||||
FeatureNumber = featureNumber,
|
||||
PartName = partName,
|
||||
IsFirstFeatureOfPart = isNewPart && f == 0,
|
||||
IsLastFeatureOnSheet = isLastFeature,
|
||||
IsSafetyHeadraise = isSafetyHeadraise && f == 0,
|
||||
IsExteriorFeature = false,
|
||||
LibraryFile = libraryFile,
|
||||
IsEtch = isEtch,
|
||||
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||
CutDistance = cutDistance,
|
||||
SheetDiagonal = sheetDiagonal
|
||||
};
|
||||
@@ -164,7 +168,7 @@ public sealed class CincinnatiSheetWriter
|
||||
{
|
||||
// Safety headraise before rapid to new part
|
||||
if (isSafetyHeadraise && _config.SafetyHeadraiseDistance.HasValue)
|
||||
w.WriteLine($"M47 P{_config.SafetyHeadraiseDistance.Value}(Safety Headraise)");
|
||||
w.WriteLine($"M47 P{_config.SafetyHeadraiseDistance.Value} (Safety Headraise)");
|
||||
|
||||
// Rapid to part position (bounding box lower-left)
|
||||
var featureNumber = featureIndex == 0
|
||||
@@ -173,21 +177,21 @@ public sealed class CincinnatiSheetWriter
|
||||
|
||||
var sb = new StringBuilder();
|
||||
if (_config.UseLineNumbers)
|
||||
sb.Append($"N{featureNumber}");
|
||||
sb.Append($"G0X{_fmt.FormatCoord(part.Left)}Y{_fmt.FormatCoord(part.Bottom)}");
|
||||
sb.Append($"N{featureNumber} ");
|
||||
sb.Append($"G0 X{_fmt.FormatCoord(part.Left)} Y{_fmt.FormatCoord(part.Bottom)}");
|
||||
w.WriteLine(sb.ToString());
|
||||
|
||||
// Part name comment
|
||||
w.WriteLine(CoordinateFormatter.Comment($"PART: {partName}"));
|
||||
|
||||
// Set local coordinate system at part position
|
||||
w.WriteLine("G92X0Y0");
|
||||
w.WriteLine("G92 X0 Y0");
|
||||
|
||||
// Call part sub-program
|
||||
w.WriteLine($"M98P{subNum}({partName})");
|
||||
w.WriteLine($"M98 P{subNum} ({partName})");
|
||||
|
||||
// Restore sheet coordinate system
|
||||
w.WriteLine($"G92X{_fmt.FormatCoord(part.Left)}Y{_fmt.FormatCoord(part.Bottom)}");
|
||||
w.WriteLine($"G92 X{_fmt.FormatCoord(part.Left)} Y{_fmt.FormatCoord(part.Bottom)}");
|
||||
|
||||
// Head raise (unless last part on sheet)
|
||||
if (!isLastPart)
|
||||
@@ -195,36 +199,22 @@ public sealed class CincinnatiSheetWriter
|
||||
}
|
||||
|
||||
private void WritePartsInline(TextWriter w, List<Part> allParts,
|
||||
string libraryFile, double sheetDiagonal)
|
||||
string cutLibrary, string etchLibrary, double sheetDiagonal)
|
||||
{
|
||||
// Multi-contour splitting
|
||||
var features = new List<(Part part, List<ICode> codes)>();
|
||||
// Split and classify features, ordering etch before cut per part
|
||||
var features = new List<(Part part, List<ICode> codes, bool isEtch)>();
|
||||
foreach (var part in allParts)
|
||||
{
|
||||
List<ICode> current = null;
|
||||
foreach (var code in part.Program.Codes)
|
||||
{
|
||||
if (code is RapidMove)
|
||||
{
|
||||
if (current != null)
|
||||
features.Add((part, current));
|
||||
current = new List<ICode> { code };
|
||||
}
|
||||
else
|
||||
{
|
||||
current ??= new List<ICode>();
|
||||
current.Add(code);
|
||||
}
|
||||
}
|
||||
if (current != null && current.Count > 0)
|
||||
features.Add((part, current));
|
||||
var partFeatures = SplitAndOrderFeatures(part);
|
||||
foreach (var (codes, isEtch) in partFeatures)
|
||||
features.Add((part, codes, isEtch));
|
||||
}
|
||||
|
||||
// Emit features
|
||||
var lastPartName = "";
|
||||
for (var i = 0; i < features.Count; i++)
|
||||
{
|
||||
var (part, codes) = features[i];
|
||||
var (part, codes, isEtch) = features[i];
|
||||
var partName = part.BaseDrawing.Name;
|
||||
var isFirstFeatureOfPart = partName != lastPartName;
|
||||
var isSafetyHeadraise = partName != lastPartName && lastPartName != "";
|
||||
@@ -245,7 +235,8 @@ public sealed class CincinnatiSheetWriter
|
||||
IsLastFeatureOnSheet = isLastFeature,
|
||||
IsSafetyHeadraise = isSafetyHeadraise,
|
||||
IsExteriorFeature = false,
|
||||
LibraryFile = libraryFile,
|
||||
IsEtch = isEtch,
|
||||
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||
CutDistance = cutDistance,
|
||||
SheetDiagonal = sheetDiagonal
|
||||
};
|
||||
@@ -255,9 +246,14 @@ public sealed class CincinnatiSheetWriter
|
||||
}
|
||||
}
|
||||
|
||||
private static List<List<ICode>> SplitPartFeatures(Part part)
|
||||
/// <summary>
|
||||
/// Splits a part's program into features (by rapids), classifies each as etch or cut,
|
||||
/// and orders etch features before cut features.
|
||||
/// </summary>
|
||||
public static List<(List<ICode> codes, bool isEtch)> SplitAndOrderFeatures(Part part)
|
||||
{
|
||||
var features = new List<List<ICode>>();
|
||||
var etchFeatures = new List<List<ICode>>();
|
||||
var cutFeatures = new List<List<ICode>>();
|
||||
List<ICode> current = null;
|
||||
|
||||
foreach (var code in part.Program.Codes)
|
||||
@@ -265,7 +261,7 @@ public sealed class CincinnatiSheetWriter
|
||||
if (code is RapidMove)
|
||||
{
|
||||
if (current != null)
|
||||
features.Add(current);
|
||||
ClassifyAndAdd(current, etchFeatures, cutFeatures);
|
||||
current = new List<ICode> { code };
|
||||
}
|
||||
else
|
||||
@@ -276,9 +272,40 @@ public sealed class CincinnatiSheetWriter
|
||||
}
|
||||
|
||||
if (current != null && current.Count > 0)
|
||||
features.Add(current);
|
||||
ClassifyAndAdd(current, etchFeatures, cutFeatures);
|
||||
|
||||
return features;
|
||||
// Etch features first, then cut features
|
||||
var result = new List<(List<ICode>, bool)>();
|
||||
foreach (var f in etchFeatures)
|
||||
result.Add((f, true));
|
||||
foreach (var f in cutFeatures)
|
||||
result.Add((f, false));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ClassifyAndAdd(List<ICode> codes,
|
||||
List<List<ICode>> etchFeatures, List<List<ICode>> cutFeatures)
|
||||
{
|
||||
if (IsFeatureEtch(codes))
|
||||
etchFeatures.Add(codes);
|
||||
else
|
||||
cutFeatures.Add(codes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A feature is etch if any non-rapid move has LayerType.Scribe.
|
||||
/// </summary>
|
||||
public static bool IsFeatureEtch(List<ICode> codes)
|
||||
{
|
||||
foreach (var code in codes)
|
||||
{
|
||||
if (code is LinearMove linear && linear.Layer == LayerType.Scribe)
|
||||
return true;
|
||||
if (code is ArcMove arc && arc.Layer == LayerType.Scribe)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static double ComputeCutDistance(List<ICode> codes)
|
||||
|
||||
Reference in New Issue
Block a user