Files
OpenNest/Posts/OpenNest.Posts.Cincinnati/CincinnatiHoleCallWriter.cs
T
ajandClaude Opus 5.5 3a7073d38c fix(posts): post Cincinnati CL holes where and as drawn, in sequence
Hole sub-programs are stored incremental but were written under G90 as
if absolute, producing invalid arcs. The registry now posts absolute
copies and leaves the nest's programs untouched.

Sheet-level hole calls omitted the part location from G52, so every
part's holes were cut near the sheet origin. Calls now shift to the
part location plus the hole offset.

Hole calls were matched to sub-programs by call id, which planned
emission numbers 1, 2, ... per drawing, so holes from different drawings
shared the first drawing's sub-program and diameter. Calls are now keyed
by program reference and deduplicated by posted toolpath.

A part opening with a hole call got a synthetic rapid that posted an
empty pierce on the part edge. The rapid is now only inserted before a
contour motion with no rapid ahead of it; the hole call carries the
part comment and safety head raise that feature used to hold.

The sheet writer re-sorted parts bottom-then-left with cut-offs last.
Plate order is the cut sequence, so parts now post in plate order.

Regression tests replay the posted G-code for off-origin parts of
several drawings and check each hole's center and radius.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 01:08:06 -04:00

75 lines
2.7 KiB
C#

using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.Posts.Cincinnati;
internal static class CincinnatiHoleCallWriter
{
/// <param name="origin">
/// Position of the calling program's frame in the current work coordinate system:
/// the part location for sheet-level calls, zero inside a normalized part sub-program.
/// </param>
/// <param name="holeSubprograms">
/// Maps each call's hole program (by reference) to its posted sub-program number.
/// Unmapped calls fall back to the call's own id.
/// </param>
/// <param name="partName">Part-name comment to emit when the call opens a part, else null.</param>
/// <param name="isSafetyHeadraise">
/// True when the call is a part's first feature after another part, matching the
/// feature writer's safety head raise.
/// </param>
internal static void Write(
TextWriter w,
SubProgramCall call,
Vector origin,
int featureIndex,
bool isLastFeature,
CincinnatiPostConfig config,
CoordinateFormatter fmt,
IReadOnlyDictionary<Program, int> holeSubprograms,
string partName = null,
bool isSafetyHeadraise = false
)
{
var postSubNum =
holeSubprograms != null
&& call.Program != null
&& holeSubprograms.TryGetValue(call.Program, out var num)
? num
: call.Id;
var featureNumber =
featureIndex == 0 ? config.FeatureLineNumberStart : 1000 + featureIndex + 1;
// Shift the local origin to the hole center via G52 (manual §1.52).
// G52 does not move the nozzle: the hole-local sub-program's first rapid
// goes directly from the previous feature's end to pierce.
// See docs/cincinnati-post-output.md for the full bracket.
var center = origin + call.Offset;
var sb = new StringBuilder();
if (config.UseLineNumbers)
sb.Append($"N{featureNumber} ");
sb.Append($"G52 X{fmt.FormatCoord(center.X)} Y{fmt.FormatCoord(center.Y)}");
w.WriteLine(sb.ToString());
if (!string.IsNullOrEmpty(partName))
w.WriteLine(CoordinateFormatter.Comment($"PART: {partName}"));
w.WriteLine($"M98 P{postSubNum}");
// Cancel the local shift (manual §1.52).
w.WriteLine("G52 X0 Y0");
if (isLastFeature)
return;
if (isSafetyHeadraise && config.SafetyHeadraiseDistance.HasValue)
w.WriteLine($"M47 P{config.SafetyHeadraiseDistance.Value} (Safety Headraise)");
else
w.WriteLine("M47");
}
}