feat(posts): trigger the Cincinnati safety head raise by rapid length

The safety raise (M47 P<SafetyHeadraiseDistance>) used to fire only when
the next part had a different drawing name, which has nothing to do with
crash risk: a long rapid between copies of the same part got a plain M47,
while a short hop between neighbouring different drawings got the full
raise. Sheets are rarely flat (5/8" plate can be ~1.5" out of flat within
supplier tolerance), and longer rapids cross more of that bow.

Add SafetyHeadraiseRapidDistance (program units, default 12"): any rapid to
the next pierce longer than it takes the safety raise, regardless of
drawing. Unknown travel never does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ajandClaude Opus 5.5 committed 2026-10-20 07:08:10 -04:00
1 parent efa4844049
commit abfc1a714a
11 files changed
+77 -51

No files matched your search

@@ -18,12 +18,6 @@ public sealed class FeatureContext
public bool IsFirstFeatureOfPart { get; set; }
public bool IsLastFeatureOnSheet { get; set; }
/// <summary>
/// True when the rapid after this feature goes to a different part, so its head
/// raise is the safety head raise.
/// </summary>
public bool IsSafetyHeadraise { get; set; }
/// <summary>True when this feature cuts the part's exterior (selects ExteriorM47).</summary>
public bool IsExteriorFeature { get; set; }
public bool IsEtch { get; set; }
@@ -32,8 +26,8 @@ public sealed class FeatureContext
public double SheetDiagonal { get; set; }
/// <summary>
/// Rapid travel from this feature's end to the next pierce, used by
/// <see cref="M47Mode.Auto"/>. Null when unknown.
/// Rapid travel from this feature's end to the next pierce, used by the safety
/// head raise and <see cref="M47Mode.Auto"/>. Null when unknown.
/// </summary>
public double? RapidDistance { get; set; }
@@ -332,12 +326,7 @@ public sealed class CincinnatiFeatureWriter
private void WriteM47(TextWriter writer, FeatureContext ctx)
{
var line = CincinnatiHeadRaise.Line(
_config,
ctx.IsSafetyHeadraise,
ctx.IsExteriorFeature,
ctx.RapidDistance
);
var line = CincinnatiHeadRaise.Line(_config, ctx.IsExteriorFeature, ctx.RapidDistance);
if (line != null)
writer.WriteLine(line);
}
@@ -5,19 +5,13 @@ namespace OpenNest.Posts.Cincinnati;
/// </summary>
internal static class CincinnatiHeadRaise
{
/// <param name="isSafetyHeadraise">True when the rapid goes to a different part.</param>
/// <param name="isExterior">True when the feature just cut is a part exterior.</param>
/// <param name="rapidDistance">Travel to the next pierce; null when unknown.</param>
/// <returns>The block to write, or null for no head raise.</returns>
internal static string Line(
CincinnatiPostConfig config,
bool isSafetyHeadraise,
bool isExterior,
double? rapidDistance
)
internal static string Line(CincinnatiPostConfig config, bool isExterior, double? rapidDistance)
{
if (isSafetyHeadraise && config.SafetyHeadraiseDistance.HasValue)
return $"M47 P{config.SafetyHeadraiseDistance.Value} (Safety Headraise)";
if (IsSafetyRapid(config, rapidDistance))
return SafetyLine(config);
var mode = isExterior ? config.ExteriorM47 : config.InteriorM47;
return mode switch
@@ -29,6 +23,19 @@ internal static class CincinnatiHeadRaise
};
}
/// <summary>
/// True when the rapid is long enough to cross warped plate, so it takes the
/// safety head raise. Unknown travel never does.
/// </summary>
internal static bool IsSafetyRapid(CincinnatiPostConfig config, double? rapidDistance) =>
config.SafetyHeadraiseDistance.HasValue
&& config.SafetyHeadraiseRapidDistance is { } threshold
&& rapidDistance is { } distance
&& distance > threshold;
internal static string SafetyLine(CincinnatiPostConfig config) =>
$"M47 P{config.SafetyHeadraiseDistance.Value} (Safety Headraise)";
// Unknown travel, or no threshold configured, always gets the full raise.
private static bool IsLongRapid(CincinnatiPostConfig config, double? rapidDistance) =>
config.M47OverrideDistanceThreshold is not { } threshold
@@ -17,9 +17,9 @@ internal static class CincinnatiHoleCallWriter
/// 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 name="rapidDistance">
/// Travel from the call to the next pierce, which selects the safety head raise;
/// null when unknown.
/// </param>
internal static void Write(
TextWriter w,
@@ -31,7 +31,7 @@ internal static class CincinnatiHoleCallWriter
CoordinateFormatter fmt,
IReadOnlyDictionary<Program, int> holeSubprograms,
string partName = null,
bool isSafetyHeadraise = false
double? rapidDistance = null
)
{
var postSubNum =
@@ -66,8 +66,8 @@ internal static class CincinnatiHoleCallWriter
if (isLastFeature)
return;
if (isSafetyHeadraise && config.SafetyHeadraiseDistance.HasValue)
w.WriteLine($"M47 P{config.SafetyHeadraiseDistance.Value} (Safety Headraise)");
if (CincinnatiHeadRaise.IsSafetyRapid(config, rapidDistance))
w.WriteLine(CincinnatiHeadRaise.SafetyLine(config));
else
w.WriteLine("M47");
}
@@ -89,7 +89,6 @@ public sealed class CincinnatiPartSubprogramWriter
PartName = drawingName,
IsFirstFeatureOfPart = false,
IsLastFeatureOnSheet = isLastFeature,
IsSafetyHeadraise = false,
IsExteriorFeature = exterior[i],
IsEtch = isEtch,
LibraryFile = isEtch ? etchLibrary : cutLibrary,
@@ -180,10 +180,17 @@ namespace OpenNest.Posts.Cincinnati
[Category("7. Safety")]
[DisplayName("Safety Headraise Distance")]
[Description(
"Safety head raise distance in machine units, posted as M47 P<distance> before the rapid to a different part. Null = disabled."
"Safety head raise distance in machine units, posted as M47 P<distance> before rapids longer than the safety headraise rapid distance. Null = disabled."
)]
public int? SafetyHeadraiseDistance { get; set; } = 2000;
[Category("7. Safety")]
[DisplayName("Safety Headraise Rapid Distance")]
[Description(
"Rapids to the next pierce longer than this get the safety head raise instead of the M47 mode's raise, so the head clears warped plate. Program units. Null = disabled."
)]
public double? SafetyHeadraiseRapidDistance { get; set; } = 12.0;
[Category("8. Pallet")]
[DisplayName("Pallet Exchange")]
[Description("When pallet exchange occurs (None, EndOfSheet, or StartAndEnd).")]
@@ -210,9 +210,8 @@ public sealed class CincinnatiSheetWriter
var next = i + 1 < items.Count ? items[i + 1] : null;
var isLastFeature = next == null;
// A head raise belongs before the rapid that leaves this item, so the safety
// raise goes on a part's last item when a different part follows.
var isSafetyHeadraise = next != null && next.Part.BaseDrawing.Name != partName;
// A head raise belongs before the rapid that leaves this item; its travel
// decides whether that raise is the safety raise.
double? rapidDistance =
next == null
? null
@@ -228,7 +227,6 @@ public sealed class CincinnatiSheetWriter
item,
i,
partName,
isSafetyHeadraise,
rapidDistance,
isLastFeature
);
@@ -250,7 +248,7 @@ public sealed class CincinnatiSheetWriter
_fmt,
_holeSubprograms,
isFirstFeatureOfPart ? partName : null,
isSafetyHeadraise
rapidDistance
);
continue;
}
@@ -262,7 +260,6 @@ public sealed class CincinnatiSheetWriter
PartName = partName,
IsFirstFeatureOfPart = isFirstFeatureOfPart,
IsLastFeatureOnSheet = isLastFeature,
IsSafetyHeadraise = isSafetyHeadraise,
IsExteriorFeature = item.IsExterior,
IsEtch = item.IsEtch,
LibraryFile = item.IsEtch ? etchLibrary : cutLibrary,
@@ -296,7 +293,6 @@ public sealed class CincinnatiSheetWriter
SheetItem item,
int index,
string partName,
bool isSafetyHeadraise,
double? rapidDistance,
bool isLastPart
)
@@ -325,7 +321,7 @@ public sealed class CincinnatiSheetWriter
// One head raise covers the rapid back and the rapid on. The part is complete,
// so the raise follows the exterior M47 mode.
var raise = CincinnatiHeadRaise.Line(_config, isSafetyHeadraise, true, rapidDistance);
var raise = CincinnatiHeadRaise.Line(_config, true, rapidDistance);
if (raise != null)
w.WriteLine(raise);
@@ -20,6 +20,7 @@
"ExteriorM47": "Always",
"M47OverrideDistanceThreshold": null,
"SafetyHeadraiseDistance": 2000,
"SafetyHeadraiseRapidDistance": 12.0,
"PalletExchange": "EndOfSheet",
"LeadInFeedratePercent": 0.5,
"LeadInArcLine2FeedratePercent": 0.5,