From e239967a7b1bc408e6df963eae1ca5dd85973e11 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 15 Apr 2026 06:17:31 -0400 Subject: [PATCH] feat(cincinnati): emit SubProgramCall features as M98 hole calls When a feature is a single SubProgramCall, wrap the call with a G52 offset shift, emit M98 P, reset G52, and add M47 between features. Accepts an optional hole subprogram id map so the post can remap drawing-local subprogram ids to machine subprogram numbers. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../CincinnatiPartSubprogramWriter.cs | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/OpenNest.Posts.Cincinnati/CincinnatiPartSubprogramWriter.cs b/OpenNest.Posts.Cincinnati/CincinnatiPartSubprogramWriter.cs index f3cc40a..7045845 100644 --- a/OpenNest.Posts.Cincinnati/CincinnatiPartSubprogramWriter.cs +++ b/OpenNest.Posts.Cincinnati/CincinnatiPartSubprogramWriter.cs @@ -16,11 +16,16 @@ public sealed class CincinnatiPartSubprogramWriter { private readonly CincinnatiPostConfig _config; private readonly CincinnatiFeatureWriter _featureWriter; + private readonly CoordinateFormatter _fmt; + private readonly Dictionary _holeSubprograms; - public CincinnatiPartSubprogramWriter(CincinnatiPostConfig config) + public CincinnatiPartSubprogramWriter(CincinnatiPostConfig config, + Dictionary holeSubprograms = null) { _config = config; _featureWriter = new CincinnatiFeatureWriter(config); + _fmt = new CoordinateFormatter(config.PostedAccuracy); + _holeSubprograms = holeSubprograms; } /// @@ -44,6 +49,15 @@ public sealed class CincinnatiPartSubprogramWriter for (var i = 0; i < ordered.Count; i++) { var (codes, isEtch) = ordered[i]; + var isLastFeature = i == ordered.Count - 1; + + // SubProgramCall features are emitted as M98 hole calls + if (codes.Count == 1 && codes[0] is SubProgramCall holeCall) + { + WriteHoleSubprogramCall(w, holeCall, i, isLastFeature); + continue; + } + var featureNumber = i == 0 ? _config.FeatureLineNumberStart : 1000 + i + 1; @@ -55,7 +69,7 @@ public sealed class CincinnatiPartSubprogramWriter FeatureNumber = featureNumber, PartName = drawingName, IsFirstFeatureOfPart = false, - IsLastFeatureOnSheet = i == ordered.Count - 1, + IsLastFeatureOnSheet = isLastFeature, IsSafetyHeadraise = false, IsExteriorFeature = false, IsEtch = isEtch, @@ -70,6 +84,30 @@ public sealed class CincinnatiPartSubprogramWriter w.WriteLine($"M99 (END OF {drawingName})"); } + 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($"G52 X{_fmt.FormatCoord(call.Offset.X)} Y{_fmt.FormatCoord(call.Offset.Y)}"); + w.WriteLine(sb.ToString()); + + w.WriteLine($"M98 P{postSubNum}"); + + w.WriteLine("G52 X0 Y0"); + + if (!isLastFeature) + w.WriteLine("M47"); + } + /// /// If the program has no leading rapid, inserts a synthetic rapid at the /// last motion endpoint (the contour return point). This ensures the feature