mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-03 12:21:39 -05:00
Merge branch 'refs/heads/claude/objective-yalow-6f6bde' into claude/help-docs-update-list-760b9c
This commit is contained in:
commit
5da1e35e88
9 files changed
+392
-26
No files matched your search
@@ -19,12 +19,15 @@ public static class PostVerificationAnalyzer
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var report = Analyze(nest, cancellationToken);
|
||||
if (postProcessor is IPostVerificationSupport { PreservesPlacedProgramOrder: true })
|
||||
var change = postProcessor is IPostVerificationSupport support
|
||||
? support.PlacedProgramOrderChange(nest)
|
||||
: "does not declare that it preserves placed part/contour order and pierce positions";
|
||||
if (change == null)
|
||||
return report;
|
||||
var findings = report.Findings.ToList();
|
||||
findings.Add(new(PostVerificationKind.Incomplete, 0, null, null,
|
||||
$"Post '{postProcessor?.Name ?? "unknown"}' does not declare that it preserves placed part/contour order " +
|
||||
"and pierce positions. Nest-level checks ran, but the final rapid sequence requires manual review."));
|
||||
$"Post '{postProcessor?.Name ?? "unknown"}' {change}. " +
|
||||
"Nest-level checks ran, but the final rapid sequence requires manual review."));
|
||||
return new PostVerificationReport(findings);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,5 +9,15 @@ namespace OpenNest;
|
||||
/// </summary>
|
||||
public interface IPostVerificationSupport
|
||||
{
|
||||
/// <summary>True when the post preserves the placed order for every nest.</summary>
|
||||
bool PreservesPlacedProgramOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Why posting <paramref name="nest"/> with the current settings would not move in the
|
||||
/// placed order, or null when it would. A post whose order depends on the nest or its
|
||||
/// settings overrides this; it must return null only when the posted motion sequence
|
||||
/// provably equals the placed one.
|
||||
/// </summary>
|
||||
string PlacedProgramOrderChange(Nest nest) =>
|
||||
PreservesPlacedProgramOrder ? null : "does not declare that it preserves placed part/contour order and pierce positions";
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC.CuttingPlanning;
|
||||
using OpenNest.Diagnostics;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Posts.Cincinnati;
|
||||
|
||||
namespace OpenNest.Tests.Cincinnati;
|
||||
|
||||
/// <summary>
|
||||
/// The CL post declares that it preserves the placed order only for a nest whose posted
|
||||
/// pierces and cuts replay exactly as the pre-post check read them, and withholds it
|
||||
/// whenever the post would change them.
|
||||
/// </summary>
|
||||
public class CincinnatiPlacedOrderTests
|
||||
{
|
||||
[Fact]
|
||||
public void ScribesFirst_PostsTheCheckedSequence_AndDeclaresIt()
|
||||
{
|
||||
var nest = Nest(scribeFirst: true);
|
||||
var post = new CincinnatiPostProcessor(CincinnatiReplay.Config(partSubprograms: false));
|
||||
|
||||
Assert.Null(post.PlacedProgramOrderChange(nest));
|
||||
Assert.Equal(
|
||||
PostVerificationAnalyzer.Analyze(nest).Findings.Select(f => f.Message),
|
||||
PostVerificationAnalyzer.AnalyzeForPost(nest, post).Findings.Select(f => f.Message));
|
||||
|
||||
var (pierces, cuts) = Checked(nest);
|
||||
Assert.Equal(4, pierces.Count); // scribe, hole, perimeter, square
|
||||
var run = CincinnatiReplay.Run(CincinnatiReplay.Post(nest, partSubprograms: false));
|
||||
AssertSame(pierces, run.PierceLog.Select(p => p.Point).ToList());
|
||||
AssertSame(cuts, run.Cuts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScribeAfterACut_WithholdsTheDeclaration_BecauseThePostCutsItFirst()
|
||||
{
|
||||
var nest = Nest(scribeFirst: false);
|
||||
var post = new CincinnatiPostProcessor(CincinnatiReplay.Config(partSubprograms: false));
|
||||
|
||||
var change = post.PlacedProgramOrderChange(nest);
|
||||
Assert.Equal("writes the etch features of part 1 on plate 1 before the cuts placed ahead of them", change);
|
||||
var report = PostVerificationAnalyzer.AnalyzeForPost(nest, post);
|
||||
Assert.Contains(report.Findings, f => f.Kind == PostVerificationKind.Incomplete && f.Message.Contains(change));
|
||||
Assert.False(report.CanPost(false));
|
||||
|
||||
// The withheld declaration is real: the posted pierces differ from the checked ones.
|
||||
var (pierces, _) = Checked(nest);
|
||||
var run = CincinnatiReplay.Run(CincinnatiReplay.Post(nest, partSubprograms: false));
|
||||
Assert.Equal(pierces.Count, run.PierceLog.Count);
|
||||
Assert.True(CincinnatiReplay.Near(new Vector(3, 3), run.PierceLog[0].Point));
|
||||
Assert.False(CincinnatiReplay.Near(pierces[0], run.PierceLog[0].Point));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PartSubprograms_WithholdTheDeclaration()
|
||||
{
|
||||
var post = new CincinnatiPostProcessor(CincinnatiReplay.Config(partSubprograms: true));
|
||||
|
||||
Assert.Contains("part sub-programs", post.PlacedProgramOrderChange(Nest(scribeFirst: true)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SmartRapids_WithholdTheDeclaration()
|
||||
{
|
||||
var config = CincinnatiReplay.Config(partSubprograms: false);
|
||||
config.UseSmartRapids = true;
|
||||
|
||||
Assert.Contains("smart rapids", new CincinnatiPostProcessor(config).PlacedProgramOrderChange(Nest(scribeFirst: true)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AContourWithoutALeadingRapid_WithholdsTheDeclaration()
|
||||
{
|
||||
// The post pierces it at its return point instead of wherever the tool already is.
|
||||
var program = new Program();
|
||||
program.LineTo(4, 0);
|
||||
program.LineTo(4, 4);
|
||||
program.LineTo(0, 4);
|
||||
program.LineTo(0, 0);
|
||||
var nest = new Nest();
|
||||
nest.CreatePlate().Parts.Add(new Part(new Drawing("square", program), new Vector(2, 2)));
|
||||
var post = new CincinnatiPostProcessor(CincinnatiReplay.Config(partSubprograms: false));
|
||||
|
||||
Assert.Equal("adds or drops rapids in part 1 on plate 1", post.PlacedProgramOrderChange(nest));
|
||||
}
|
||||
|
||||
// A 20x20 plate part at (1, 1) with a scribe, a hole cut through a hole call and its
|
||||
// perimeter, then a 4x4 square at (30, 1). The scribe comes first or after the hole.
|
||||
private static Nest Nest(bool scribeFirst)
|
||||
{
|
||||
var clean = new Program();
|
||||
clean.MoveTo(2, 2);
|
||||
clean.Codes.Add(new LinearMove(6, 2) { Layer = LayerType.Scribe });
|
||||
clean.MoveTo(13, 10);
|
||||
clean.ArcTo(13, 10, 10, 10, RotationType.CCW);
|
||||
clean.MoveTo(20, 10);
|
||||
Outline(clean, 20);
|
||||
|
||||
var hole = new Program();
|
||||
hole.MoveTo(0, 0);
|
||||
hole.Codes.Add(new LinearMove(3, 0) { Layer = LayerType.Leadin });
|
||||
hole.Codes.Add(new ArcMove(new Vector(3, 0), Vector.Zero, RotationType.CCW));
|
||||
var placed = new Program();
|
||||
if (scribeFirst)
|
||||
Scribe(placed);
|
||||
placed.Codes.Add(new SubProgramCall { Id = 1, Program = hole, Offset = new Vector(10, 10) });
|
||||
if (!scribeFirst)
|
||||
Scribe(placed);
|
||||
placed.MoveTo(20.5, 10);
|
||||
placed.Codes.Add(new LinearMove(20, 10) { Layer = LayerType.Leadin });
|
||||
Outline(placed, 20);
|
||||
|
||||
var plate = new Part(new Drawing("plate", clean), new Vector(1, 1));
|
||||
Assert.True(plate.RestoreLeadInProgram(placed, false));
|
||||
|
||||
var square = new Program();
|
||||
square.MoveTo(4, 2);
|
||||
Outline(square, 4);
|
||||
var leaded = new Program();
|
||||
leaded.MoveTo(4.5, 2);
|
||||
leaded.Codes.Add(new LinearMove(4, 2) { Layer = LayerType.Leadin });
|
||||
Outline(leaded, 4);
|
||||
var second = new Part(new Drawing("square", square), new Vector(30, 1));
|
||||
Assert.True(second.RestoreLeadInProgram(leaded, false));
|
||||
|
||||
var nest = new Nest();
|
||||
var sheet = nest.CreatePlate();
|
||||
sheet.Size = new Size(60, 120);
|
||||
sheet.Parts.Add(plate);
|
||||
sheet.Parts.Add(second);
|
||||
return nest;
|
||||
|
||||
static void Scribe(Program program)
|
||||
{
|
||||
program.MoveTo(2, 2);
|
||||
program.Codes.Add(new LinearMove(6, 2) { Layer = LayerType.Scribe });
|
||||
}
|
||||
|
||||
// A size x size square cut counterclockwise from its right-edge midpoint.
|
||||
static void Outline(Program program, double size)
|
||||
{
|
||||
program.LineTo(size, size);
|
||||
program.LineTo(0, size);
|
||||
program.LineTo(0, 0);
|
||||
program.LineTo(size, 0);
|
||||
program.LineTo(size, size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// The pierces and cuts the pre-post check reads, in plate order and plate coordinates.
|
||||
private static (List<Vector> Pierces, List<(Vector From, Vector To)> Cuts) Checked(Nest nest)
|
||||
{
|
||||
var pierces = new List<Vector>();
|
||||
var cuts = new List<(Vector, Vector)>();
|
||||
Vector? position = Vector.Zero;
|
||||
foreach (var part in nest.Plates[0].Parts)
|
||||
{
|
||||
var moves = ExecutionMotionReader.Read(part.Program, part.Location, position, default).Motions;
|
||||
for (var i = 0; i < moves.Count; i++)
|
||||
{
|
||||
if (!moves[i].Rapid)
|
||||
cuts.Add((moves[i].Start!.Value, moves[i].End));
|
||||
else if (i + 1 < moves.Count && !moves[i + 1].Rapid)
|
||||
pierces.Add(moves[i].End);
|
||||
}
|
||||
position = moves[^1].End;
|
||||
}
|
||||
return (pierces, cuts);
|
||||
}
|
||||
|
||||
private static void AssertSame(List<Vector> expected, List<Vector> actual)
|
||||
{
|
||||
Assert.Equal(expected.Count, actual.Count);
|
||||
for (var i = 0; i < expected.Count; i++)
|
||||
Assert.True(CincinnatiReplay.Near(expected[i], actual[i]), $"Pierce {i}: {expected[i]} vs {actual[i]}");
|
||||
}
|
||||
|
||||
private static void AssertSame(List<(Vector From, Vector To)> expected, List<(Vector From, Vector To)> actual)
|
||||
{
|
||||
Assert.Equal(expected.Count, actual.Count);
|
||||
for (var i = 0; i < expected.Count; i++)
|
||||
Assert.True(
|
||||
CincinnatiReplay.Near(expected[i].From, actual[i].From) && CincinnatiReplay.Near(expected[i].To, actual[i].To),
|
||||
$"Cut {i}: {expected[i]} vs {actual[i]}");
|
||||
}
|
||||
}
|
||||
@@ -10,23 +10,30 @@ namespace OpenNest.Tests.Diagnostics;
|
||||
public class PostVerificationPostContractTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReorderingPostsRequireAcknowledgmentEvenWhenNestChecksAreClear()
|
||||
public void ReorderingPostRequiresAcknowledgmentEvenWhenNestChecksAreClear()
|
||||
{
|
||||
var nest = ClearNest();
|
||||
Assert.False(PostVerificationAnalyzer.Analyze(nest).HasWarnings);
|
||||
IPostProcessor[] posts =
|
||||
[
|
||||
new CincinnatiPostProcessor(new CincinnatiPostConfig()),
|
||||
new GravographISPostProcessor(new GravographISPostConfig())
|
||||
];
|
||||
foreach (var post in posts)
|
||||
{
|
||||
var report = PostVerificationAnalyzer.AnalyzeForPost(nest, post);
|
||||
Assert.Contains(report.Findings, finding => finding.Kind == PostVerificationKind.Incomplete);
|
||||
Assert.False(report.CanPost(false));
|
||||
Assert.True(report.CanPost(true));
|
||||
Assert.Contains(post.Name, report.ToDisplayText());
|
||||
}
|
||||
var post = new GravographISPostProcessor(new GravographISPostConfig());
|
||||
var report = PostVerificationAnalyzer.AnalyzeForPost(nest, post);
|
||||
Assert.Contains(report.Findings, finding => finding.Kind == PostVerificationKind.Incomplete);
|
||||
Assert.False(report.CanPost(false));
|
||||
Assert.True(report.CanPost(true));
|
||||
Assert.Contains(post.Name, report.ToDisplayText());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CincinnatiClDeclaresPlacedOrderOnlyWhenThisNestPostsInIt()
|
||||
{
|
||||
var report = PostVerificationAnalyzer.AnalyzeForPost(ClearNest(),
|
||||
new CincinnatiPostProcessor(new CincinnatiPostConfig()));
|
||||
Assert.Empty(report.Findings);
|
||||
Assert.True(report.CanPost(false));
|
||||
|
||||
var subprograms = PostVerificationAnalyzer.AnalyzeForPost(ClearNest(),
|
||||
new CincinnatiPostProcessor(new CincinnatiPostConfig { UsePartSubprograms = true }));
|
||||
Assert.Contains(subprograms.Findings, finding => finding.Kind == PostVerificationKind.Incomplete);
|
||||
Assert.False(subprograms.CanPost(false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -163,6 +163,17 @@ public sealed class CincinnatiPartSubprogramWriter
|
||||
return (mapping, entries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The absolute copy a hole sub-program is written from: suppressed moves left uncut and
|
||||
/// a leading rapid ensured. <see cref="Write"/> then puts its etch features first.
|
||||
/// </summary>
|
||||
internal static Program PostedHole(Program hole)
|
||||
{
|
||||
var absolute = PostedCopy(hole);
|
||||
EnsureLeadingRapid(absolute);
|
||||
return absolute;
|
||||
}
|
||||
|
||||
// An absolute copy with suppressed moves left uncut, as the post writes it.
|
||||
private static Program PostedCopy(Program program)
|
||||
{
|
||||
@@ -203,8 +214,7 @@ public sealed class CincinnatiPartSubprogramWriter
|
||||
if (mapping.ContainsKey(call.Program) || !FeatureUtils.HasCut(call.Program))
|
||||
continue;
|
||||
|
||||
var absolute = PostedCopy(call.Program);
|
||||
EnsureLeadingRapid(absolute);
|
||||
var absolute = PostedHole(call.Program);
|
||||
|
||||
var canonical = ProgramToCanonical(absolute);
|
||||
if (!contentIndex.TryGetValue(canonical, out var subNum))
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Posts.Cincinnati;
|
||||
|
||||
/// <summary>
|
||||
/// Decides whether the post moves through a nest in the placed order the pre-post check
|
||||
/// verified (see docs/post-verification.md). The writer keeps plate order, but writes each
|
||||
/// part's (and hole's) etch features before its cut features, adds a rapid ahead of a contour
|
||||
/// that has none, drops rapids with nothing to cut and, with part sub-programs, rapids to and
|
||||
/// from each part's corner. The order is declared preserved only when, for this nest and
|
||||
/// these settings, none of that changes a single motion.
|
||||
/// </summary>
|
||||
internal static class CincinnatiPlacedOrder
|
||||
{
|
||||
/// <summary>Why the posted motion sequence differs from the placed one, or null when it equals it.</summary>
|
||||
internal static string Change(Nest nest, CincinnatiPostConfig config)
|
||||
{
|
||||
if (config.UsePartSubprograms)
|
||||
return "calls part sub-programs, which add rapids to and from each part's corner";
|
||||
if (config.UseSmartRapids)
|
||||
return "uses smart rapids (G121), whose blended rapids leave the straight line";
|
||||
if (nest?.Plates == null)
|
||||
return null;
|
||||
|
||||
for (var plateIndex = 0; plateIndex < nest.Plates.Count; plateIndex++)
|
||||
{
|
||||
var parts = nest.Plates[plateIndex]?.Parts;
|
||||
if (parts == null)
|
||||
continue;
|
||||
for (var partIndex = 0; partIndex < parts.Count; partIndex++)
|
||||
{
|
||||
var part = parts[partIndex];
|
||||
if (part?.Program == null)
|
||||
continue;
|
||||
var where = $"part {partIndex + 1} on plate {plateIndex + 1}";
|
||||
List<ICode> placed;
|
||||
List<(List<ICode> codes, bool isEtch)> posted;
|
||||
try
|
||||
{
|
||||
placed = FeatureUtils.AbsoluteCodes(FeatureUtils.PostedProgram(part));
|
||||
posted = FeatureUtils.SplitAndClassify(part);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return $"cannot post {where} as placed";
|
||||
}
|
||||
|
||||
var change = ChangeTo(placed, posted, where);
|
||||
if (change != null)
|
||||
return change;
|
||||
|
||||
foreach (var call in placed.OfType<SubProgramCall>())
|
||||
{
|
||||
if (call.Program == null || !FeatureUtils.HasCut(call.Program))
|
||||
continue;
|
||||
var hole = CincinnatiPartSubprogramWriter.PostedHole(call.Program);
|
||||
change = ChangeTo(
|
||||
FeatureUtils.AbsoluteCodes(call.Program),
|
||||
FeatureUtils.ClassifyAndOrder(FeatureUtils.SplitByRapids(hole.Codes)),
|
||||
$"a hole of {where}"
|
||||
);
|
||||
if (change != null)
|
||||
return change;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string ChangeTo(List<ICode> placed, List<(List<ICode> codes, bool isEtch)> posted, string where)
|
||||
{
|
||||
if (SameMotions(placed, posted.SelectMany(feature => feature.codes)))
|
||||
return null;
|
||||
var cutSeen = false;
|
||||
foreach (var feature in FeatureUtils.SplitByRapids(placed))
|
||||
{
|
||||
var isEtch = FeatureUtils.IsEtch(feature);
|
||||
if (isEtch && cutSeen)
|
||||
return $"writes the etch features of {where} before the cuts placed ahead of them";
|
||||
cutSeen |= !isEtch;
|
||||
}
|
||||
return $"adds or drops rapids in {where}";
|
||||
}
|
||||
|
||||
// Motions and hole calls in the same order with exactly the same coordinates.
|
||||
private static bool SameMotions(IEnumerable<ICode> placed, IEnumerable<ICode> posted)
|
||||
{
|
||||
var a = placed.Where(code => code is Motion or SubProgramCall).ToList();
|
||||
var b = posted.Where(code => code is Motion or SubProgramCall).ToList();
|
||||
if (a.Count != b.Count)
|
||||
return false;
|
||||
for (var i = 0; i < a.Count; i++)
|
||||
{
|
||||
if (!Same(a[i], b[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool Same(ICode a, ICode b) =>
|
||||
(a, b) switch
|
||||
{
|
||||
(SubProgramCall x, SubProgramCall y) => ReferenceEquals(x.Program, y.Program) && Exact(x.Offset, y.Offset),
|
||||
(ArcMove x, ArcMove y) => Exact(x.EndPoint, y.EndPoint)
|
||||
&& Exact(x.CenterPoint, y.CenterPoint)
|
||||
&& x.Rotation == y.Rotation,
|
||||
(Motion x, Motion y) => x.GetType() == y.GetType() && Exact(x.EndPoint, y.EndPoint),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private static bool Exact(Vector a, Vector b) => a.X == b.X && a.Y == b.Y;
|
||||
}
|
||||
@@ -13,7 +13,8 @@ namespace OpenNest.Posts.Cincinnati
|
||||
public sealed class CincinnatiPostProcessor
|
||||
: IJsonConfigPostProcessor,
|
||||
IPostProcessorNestAware,
|
||||
IMaterialProvidingPostProcessor
|
||||
IMaterialProvidingPostProcessor,
|
||||
IPostVerificationSupport
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -25,6 +26,15 @@ namespace OpenNest.Posts.Cincinnati
|
||||
public string Author => "OpenNest";
|
||||
public string Description => "Cincinnati CL-707/CL-800/CL-900/CL-940/CLX family";
|
||||
|
||||
/// <summary>Etch-first writing and part sub-programs can change it; see <see cref="PlacedProgramOrderChange"/>.</summary>
|
||||
public bool PreservesPlacedProgramOrder => false;
|
||||
|
||||
/// <summary>
|
||||
/// Null when this nest posts with the current settings in exactly its placed motion order,
|
||||
/// which Plan Cutting's scribes-first programs do; otherwise what the post changes.
|
||||
/// </summary>
|
||||
public string PlacedProgramOrderChange(Nest nest) => CincinnatiPlacedOrder.Change(nest, Config);
|
||||
|
||||
public CincinnatiPostConfig Config { get; private set; }
|
||||
|
||||
object IConfigurablePostProcessor.Config => Config;
|
||||
|
||||
@@ -60,6 +60,21 @@ Sub-program bodies start with a `:<subNum>` label and end with `M99`.
|
||||
The post reads part programs through absolute-mode copies and never changes the
|
||||
nest: an incremental part program stays incremental after posting.
|
||||
|
||||
### Order and pre-post verification
|
||||
|
||||
Within a part (and within each hole sub-program) the post writes etch features
|
||||
before cut features, keeping each group in program order (`FeatureUtils.ClassifyAndOrder`).
|
||||
It also inserts a rapid to a contour's return point when the contour opens without
|
||||
one, and drops rapids with nothing to cut. Part sub-programs add a rapid to each
|
||||
part's toolpath corner and back, and smart rapids blend rapids off the straight line.
|
||||
The post declares that it preserves the placed order to
|
||||
[pre-post verification](post-verification.md#selected-post-and-cut-order)
|
||||
(`CincinnatiPlacedOrder`) only when, for the nest and settings being posted, none of
|
||||
these changes a motion: part sub-programs and smart rapids off, and each part's and
|
||||
hole's posted motions exactly equal to its placed ones. Plan Cutting already cuts a
|
||||
part's scribes first, so planned nests qualify; otherwise posting warns that the
|
||||
final rapid sequence is unverified. Output is the same either way.
|
||||
|
||||
### Sheet header
|
||||
|
||||
Each sheet sub-program re-anchors the work frame with `G92 X#5021 Y#5022`
|
||||
|
||||
@@ -198,11 +198,17 @@ the gate is installed at OpenNest's desktop and console posting entry points.
|
||||
|
||||
Cincinnati CI Fiber preserves the placed part/contour order and pierce positions
|
||||
used by this check. Cincinnati CL keeps placed part order, including cut-offs, but
|
||||
puts etch features before cut features and can add subprogram routing moves;
|
||||
Gravograph builds and optimizes separate tool passes. Those two posts, and unknown
|
||||
plugins, therefore show an additional **incomplete final rapid sequence** warning
|
||||
requiring acknowledgment even if the nest-level checks have no findings. This
|
||||
change does not silently alter their existing CNC output order.
|
||||
writes each part's (and hole's) etch features before its cut features, adds a rapid
|
||||
ahead of a contour that opens without one, drops rapids with nothing to cut, and with
|
||||
**Use part sub-programs** rapids to and from each part's corner. It declares the
|
||||
placed order for a nest only when none of that changes a single motion with the
|
||||
settings being posted: part sub-programs and smart rapids off, and every part's and
|
||||
hole's motions, compared exactly, unchanged by its writer. Plan Cutting's programs
|
||||
cut a part's scribes first, so a planned nest qualifies. Otherwise the warning names
|
||||
the first part it changes, and the CNC output order is left as it was.
|
||||
Gravograph builds and optimizes separate tool passes. It, a CL nest that does not
|
||||
qualify, and unknown plugins show an additional **incomplete final rapid sequence**
|
||||
warning requiring acknowledgment even if the nest-level checks have no findings.
|
||||
|
||||
## Limits and maintainer contract
|
||||
|
||||
@@ -218,7 +224,10 @@ its report exposes findings, display text, and `CanPost(risksAcknowledged)`. Use
|
||||
`AnalyzeForPost` at posting entry points so unknown post ordering is not silently
|
||||
certified. `IPostVerificationSupport.PreservesPlacedProgramOrder` is an opt-in
|
||||
contract, not a machine-safety claim; only declare it after verifying the writer's
|
||||
part order, contour order, and pierce positions. The WinForms dialog renders the
|
||||
part order, contour order, and pierce positions. A post whose order depends on the
|
||||
nest or its settings overrides `PlacedProgramOrderChange(nest)` instead, returning
|
||||
null only when the posted motion sequence provably equals the placed one and
|
||||
otherwise what it changes; the analyzer shows that reason in the warning. The WinForms dialog renders the
|
||||
report and enforces per-attempt consent. New callers
|
||||
must keep inputs stable until analysis completes, honor cancellation, and never
|
||||
treat incomplete analysis as a clear result. Keep analysis out of paint handlers.
|
||||
|
||||
Reference in new issue
Block a user