mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 08:21:05 -04:00
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>
75 lines
2.7 KiB
C#
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");
|
|
}
|
|
}
|