feat: handle SubProgramCalls in Cincinnati post feature splitting
SubProgramCalls are now treated as standalone features in the Cincinnati post-processor. SplitByRapids emits them as single-element features instead of splitting on rapids within sub-programs. A nest-level hole sub-program registry deduplicates by content and assigns post numbers. Sheet writers emit M98 calls with X/Y offsets for hole features, and hole sub-program definitions are written after part sub-programs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using OpenNest.CNC;
|
using OpenNest.CNC;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
@@ -136,4 +137,61 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
|
|
||||||
return (mapping, entries);
|
return (mapping, entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scans all parts across all plates and builds a nest-level registry of unique
|
||||||
|
/// hole sub-programs. Deduplicates by comparing sub-program code content.
|
||||||
|
/// </summary>
|
||||||
|
internal static (Dictionary<int, int> modelToPostMapping, List<(int subNum, Program program)> entries)
|
||||||
|
BuildHoleRegistry(IEnumerable<Plate> plates, int startNumber)
|
||||||
|
{
|
||||||
|
var mapping = new Dictionary<int, int>();
|
||||||
|
var entries = new List<(int, Program)>();
|
||||||
|
var contentIndex = new Dictionary<string, int>();
|
||||||
|
var nextSubNum = startNumber;
|
||||||
|
|
||||||
|
foreach (var plate in plates)
|
||||||
|
{
|
||||||
|
foreach (var part in plate.Parts)
|
||||||
|
{
|
||||||
|
if (part.BaseDrawing.IsCutOff) continue;
|
||||||
|
foreach (var code in part.Program.Codes)
|
||||||
|
{
|
||||||
|
if (code is not SubProgramCall call) continue;
|
||||||
|
if (mapping.ContainsKey(call.Id)) continue;
|
||||||
|
|
||||||
|
var canonical = ProgramToCanonical(call.Program);
|
||||||
|
if (contentIndex.TryGetValue(canonical, out var existingNum))
|
||||||
|
{
|
||||||
|
mapping[call.Id] = existingNum;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var subNum = nextSubNum++;
|
||||||
|
mapping[call.Id] = subNum;
|
||||||
|
contentIndex[canonical] = subNum;
|
||||||
|
entries.Add((subNum, call.Program));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mapping, entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ProgramToCanonical(Program pgm)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(pgm.Mode == Mode.Absolute ? "A" : "I");
|
||||||
|
foreach (var code in pgm.Codes)
|
||||||
|
{
|
||||||
|
if (code is LinearMove lm)
|
||||||
|
sb.Append($"L{lm.EndPoint.X:F6},{lm.EndPoint.Y:F6},{(int)lm.Layer}");
|
||||||
|
else if (code is ArcMove am)
|
||||||
|
sb.Append($"A{am.EndPoint.X:F6},{am.EndPoint.Y:F6},{am.CenterPoint.X:F6},{am.CenterPoint.Y:F6},{(int)am.Rotation},{(int)am.Layer}");
|
||||||
|
else if (code is RapidMove rm)
|
||||||
|
sb.Append($"R{rm.EndPoint.X:F6},{rm.EndPoint.Y:F6}");
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,9 +89,15 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
if (Config.UsePartSubprograms)
|
if (Config.UsePartSubprograms)
|
||||||
(partSubprograms, subprogramEntries) = CincinnatiPartSubprogramWriter.BuildRegistry(plates, Config.PartSubprogramStart);
|
(partSubprograms, subprogramEntries) = CincinnatiPartSubprogramWriter.BuildRegistry(plates, Config.PartSubprogramStart);
|
||||||
|
|
||||||
|
// 5b. Build hole sub-program registry (SubProgramCalls across all parts)
|
||||||
|
var holeStartNumber = Config.PartSubprogramStart
|
||||||
|
+ (subprogramEntries?.Count ?? 0);
|
||||||
|
var (holeMapping, holeEntries) = CincinnatiPartSubprogramWriter.BuildHoleRegistry(plates, holeStartNumber);
|
||||||
|
|
||||||
// 6. Create writers
|
// 6. Create writers
|
||||||
var preamble = new CincinnatiPreambleWriter(Config);
|
var preamble = new CincinnatiPreambleWriter(Config);
|
||||||
var sheetWriter = new CincinnatiSheetWriter(Config, vars);
|
var sheetWriter = new CincinnatiSheetWriter(Config, vars,
|
||||||
|
holeMapping.Count > 0 ? holeMapping : null);
|
||||||
|
|
||||||
// 7. Build material description from nest
|
// 7. Build material description from nest
|
||||||
var material = nest.Material;
|
var material = nest.Material;
|
||||||
@@ -135,6 +141,23 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hole sub-programs (SubProgramCall definitions)
|
||||||
|
if (holeEntries.Count > 0)
|
||||||
|
{
|
||||||
|
var holeSubWriter = new CincinnatiPartSubprogramWriter(Config);
|
||||||
|
var sheetDiagonal = firstPlate != null
|
||||||
|
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
|
||||||
|
+ firstPlate.Size.Length * firstPlate.Size.Length)
|
||||||
|
: 100.0;
|
||||||
|
|
||||||
|
foreach (var (subNum, pgm) in holeEntries)
|
||||||
|
{
|
||||||
|
CincinnatiPartSubprogramWriter.EnsureLeadingRapid(pgm);
|
||||||
|
holeSubWriter.Write(writer, pgm, "HOLE", subNum,
|
||||||
|
initialCutLibrary, etchLibrary, sheetDiagonal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writer.Flush();
|
writer.Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,16 @@ public sealed class CincinnatiSheetWriter
|
|||||||
private readonly ProgramVariableManager _vars;
|
private readonly ProgramVariableManager _vars;
|
||||||
private readonly CoordinateFormatter _fmt;
|
private readonly CoordinateFormatter _fmt;
|
||||||
private readonly CincinnatiFeatureWriter _featureWriter;
|
private readonly CincinnatiFeatureWriter _featureWriter;
|
||||||
|
private readonly Dictionary<int, int> _holeSubprograms;
|
||||||
|
|
||||||
public CincinnatiSheetWriter(CincinnatiPostConfig config, ProgramVariableManager vars)
|
public CincinnatiSheetWriter(CincinnatiPostConfig config, ProgramVariableManager vars,
|
||||||
|
Dictionary<int, int> holeSubprograms = null)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_vars = vars;
|
_vars = vars;
|
||||||
_fmt = new CoordinateFormatter(config.PostedAccuracy);
|
_fmt = new CoordinateFormatter(config.PostedAccuracy);
|
||||||
_featureWriter = new CincinnatiFeatureWriter(config);
|
_featureWriter = new CincinnatiFeatureWriter(config);
|
||||||
|
_holeSubprograms = holeSubprograms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -132,11 +135,21 @@ public sealed class CincinnatiSheetWriter
|
|||||||
for (var f = 0; f < features.Count; f++)
|
for (var f = 0; f < features.Count; f++)
|
||||||
{
|
{
|
||||||
var (codes, isEtch) = features[f];
|
var (codes, isEtch) = features[f];
|
||||||
|
var isLastFeature = isLastPart && f == features.Count - 1;
|
||||||
|
|
||||||
|
// SubProgramCall features are emitted as M98 hole calls
|
||||||
|
if (codes.Count == 1 && codes[0] is SubProgramCall holeCall)
|
||||||
|
{
|
||||||
|
WriteHoleSubprogramCall(w, holeCall, featureIndex, isLastFeature);
|
||||||
|
featureIndex++;
|
||||||
|
lastPartName = partName;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var featureNumber = featureIndex == 0
|
var featureNumber = featureIndex == 0
|
||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + featureIndex + 1;
|
: 1000 + featureIndex + 1;
|
||||||
|
|
||||||
var isLastFeature = isLastPart && f == features.Count - 1;
|
|
||||||
var cutDistance = FeatureUtils.ComputeCutDistance(codes);
|
var cutDistance = FeatureUtils.ComputeCutDistance(codes);
|
||||||
|
|
||||||
var ctx = new FeatureContext
|
var ctx = new FeatureContext
|
||||||
@@ -204,6 +217,25 @@ public sealed class CincinnatiSheetWriter
|
|||||||
w.WriteLine("M47");
|
w.WriteLine("M47");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteHoleSubprogramCall(TextWriter w, SubProgramCall call, int featureIndex, bool isLastFeature)
|
||||||
|
{
|
||||||
|
var postSubNum = _holeSubprograms != null && _holeSubprograms.TryGetValue(call.Id, out var num)
|
||||||
|
? num : call.Id;
|
||||||
|
|
||||||
|
var featureNumber = featureIndex == 0
|
||||||
|
? _config.FeatureLineNumberStart
|
||||||
|
: 1000 + featureIndex + 1;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
if (_config.UseLineNumbers)
|
||||||
|
sb.Append($"N{featureNumber} ");
|
||||||
|
sb.Append($"M98 P{postSubNum} X{_fmt.FormatCoord(call.Offset.X)} Y{_fmt.FormatCoord(call.Offset.Y)}");
|
||||||
|
w.WriteLine(sb.ToString());
|
||||||
|
|
||||||
|
if (!isLastFeature)
|
||||||
|
w.WriteLine("M47");
|
||||||
|
}
|
||||||
|
|
||||||
private void WritePartsInline(TextWriter w, List<Part> allParts,
|
private void WritePartsInline(TextWriter w, List<Part> allParts,
|
||||||
string cutLibrary, string etchLibrary, double sheetDiagonal,
|
string cutLibrary, string etchLibrary, double sheetDiagonal,
|
||||||
double plateWidth, double plateLength,
|
double plateWidth, double plateLength,
|
||||||
@@ -228,6 +260,14 @@ public sealed class CincinnatiSheetWriter
|
|||||||
var isSafetyHeadraise = partName != lastPartName && lastPartName != "";
|
var isSafetyHeadraise = partName != lastPartName && lastPartName != "";
|
||||||
var isLastFeature = i == features.Count - 1;
|
var isLastFeature = i == features.Count - 1;
|
||||||
|
|
||||||
|
// SubProgramCall features are emitted as M98 hole calls
|
||||||
|
if (codes.Count == 1 && codes[0] is SubProgramCall holeCall)
|
||||||
|
{
|
||||||
|
WriteHoleSubprogramCall(w, holeCall, i, isLastFeature);
|
||||||
|
lastPartName = partName;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var featureNumber = i == 0
|
var featureNumber = i == 0
|
||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + i + 1;
|
: 1000 + i + 1;
|
||||||
|
|||||||
@@ -21,7 +21,16 @@ public static class FeatureUtils
|
|||||||
|
|
||||||
foreach (var code in codes)
|
foreach (var code in codes)
|
||||||
{
|
{
|
||||||
if (code is RapidMove)
|
if (code is SubProgramCall)
|
||||||
|
{
|
||||||
|
// Flush any pending feature
|
||||||
|
if (current != null)
|
||||||
|
features.Add(current);
|
||||||
|
// SubProgramCall is its own feature
|
||||||
|
features.Add(new List<ICode> { code });
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
else if (code is RapidMove)
|
||||||
{
|
{
|
||||||
if (current != null)
|
if (current != null)
|
||||||
features.Add(current);
|
features.Add(current);
|
||||||
|
|||||||
Reference in New Issue
Block a user