style: apply CSharpier formatting to files merged from arc-tangency branch
This commit is contained in:
@@ -95,7 +95,16 @@ namespace OpenNest.Converters
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pgm.Codes.Add(new ArcMove(endpt, arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW) { Layer = layer });
|
pgm.Codes.Add(
|
||||||
|
new ArcMove(
|
||||||
|
endpt,
|
||||||
|
arc.Center,
|
||||||
|
arc.IsReversed ? RotationType.CW : RotationType.CCW
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Layer = layer,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastpt;
|
return lastpt;
|
||||||
@@ -108,7 +117,12 @@ namespace OpenNest.Converters
|
|||||||
if (startpt.DistanceTo(lastpt) > Tolerance.ChainTolerance)
|
if (startpt.DistanceTo(lastpt) > Tolerance.ChainTolerance)
|
||||||
pgm.MoveTo(startpt);
|
pgm.MoveTo(startpt);
|
||||||
|
|
||||||
pgm.Codes.Add(new ArcMove(startpt, circle.Center, circle.Rotation) { Layer = ClassifyLayer(circle) });
|
pgm.Codes.Add(
|
||||||
|
new ArcMove(startpt, circle.Center, circle.Rotation)
|
||||||
|
{
|
||||||
|
Layer = ClassifyLayer(circle),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
lastpt = startpt;
|
lastpt = startpt;
|
||||||
return lastpt;
|
return lastpt;
|
||||||
@@ -130,8 +144,10 @@ namespace OpenNest.Converters
|
|||||||
private static LayerType ClassifyLayer(Entity geo)
|
private static LayerType ClassifyLayer(Entity geo)
|
||||||
{
|
{
|
||||||
var name = geo.Layer?.Name;
|
var name = geo.Layer?.Name;
|
||||||
if (string.Equals(name, "ENGRAVE", System.StringComparison.OrdinalIgnoreCase) ||
|
if (
|
||||||
string.Equals(name, "ETCH", System.StringComparison.OrdinalIgnoreCase))
|
string.Equals(name, "ENGRAVE", System.StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(name, "ETCH", System.StringComparison.OrdinalIgnoreCase)
|
||||||
|
)
|
||||||
return LayerType.Scribe;
|
return LayerType.Scribe;
|
||||||
|
|
||||||
return LayerType.Cut;
|
return LayerType.Cut;
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ namespace OpenNest.Geometry
|
|||||||
/// the arc passes through both endpoints and departs P1 in the given direction.
|
/// the arc passes through both endpoints and departs P1 in the given direction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static (Vector center, double radius, double deviation) FitWithStartTangent(
|
internal static (Vector center, double radius, double deviation) FitWithStartTangent(
|
||||||
List<Vector> points, Vector tangent)
|
List<Vector> points,
|
||||||
|
Vector tangent
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (points.Count < 3)
|
if (points.Count < 3)
|
||||||
return (Vector.Invalid, 0, double.MaxValue);
|
return (Vector.Invalid, 0, double.MaxValue);
|
||||||
@@ -64,8 +66,15 @@ namespace OpenNest.Geometry
|
|||||||
/// two requested ones — when the requested tangents are consistent with a single
|
/// two requested ones — when the requested tangents are consistent with a single
|
||||||
/// circular arc, both are matched exactly.
|
/// circular arc, both are matched exactly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static (Vector center, double radius, double deviation) FitThroughEndpointsWithTangents(
|
internal static (
|
||||||
List<Vector> points, Vector startTangent, Vector endTangent)
|
Vector center,
|
||||||
|
double radius,
|
||||||
|
double deviation
|
||||||
|
) FitThroughEndpointsWithTangents(
|
||||||
|
List<Vector> points,
|
||||||
|
Vector startTangent,
|
||||||
|
Vector endTangent
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (points.Count < 3)
|
if (points.Count < 3)
|
||||||
return (Vector.Invalid, 0, double.MaxValue);
|
return (Vector.Invalid, 0, double.MaxValue);
|
||||||
@@ -104,14 +113,20 @@ namespace OpenNest.Geometry
|
|||||||
private static double SignedAngle(double ux, double uy, Vector to)
|
private static double SignedAngle(double ux, double uy, Vector to)
|
||||||
{
|
{
|
||||||
var len = System.Math.Sqrt(to.X * to.X + to.Y * to.Y);
|
var len = System.Math.Sqrt(to.X * to.X + to.Y * to.Y);
|
||||||
if (len < 1e-10) return 0;
|
if (len < 1e-10)
|
||||||
|
return 0;
|
||||||
return System.Math.Atan2(ux * to.Y - uy * to.X, ux * to.X + uy * to.Y);
|
return System.Math.Atan2(ux * to.Y - uy * to.X, ux * to.X + uy * to.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes the maximum radial deviation of interior points from a circle.
|
/// Computes the maximum radial deviation of interior points from a circle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static double MaxRadialDeviation(List<Vector> points, double cx, double cy, double radius)
|
internal static double MaxRadialDeviation(
|
||||||
|
List<Vector> points,
|
||||||
|
double cx,
|
||||||
|
double cy,
|
||||||
|
double radius
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var maxDev = 0.0;
|
var maxDev = 0.0;
|
||||||
for (var i = 1; i < points.Count - 1; i++)
|
for (var i = 1; i < points.Count - 1; i++)
|
||||||
@@ -120,7 +135,8 @@ namespace OpenNest.Geometry
|
|||||||
var py = points[i].Y - cy;
|
var py = points[i].Y - cy;
|
||||||
var dist = System.Math.Sqrt(px * px + py * py);
|
var dist = System.Math.Sqrt(px * px + py * py);
|
||||||
var dev = System.Math.Abs(dist - radius);
|
var dev = System.Math.Abs(dist - radius);
|
||||||
if (dev > maxDev) maxDev = dev;
|
if (dev > maxDev)
|
||||||
|
maxDev = dev;
|
||||||
}
|
}
|
||||||
return maxDev;
|
return maxDev;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ public class ArcCandidate
|
|||||||
public double MaxDeviation { get; set; }
|
public double MaxDeviation { get; set; }
|
||||||
public Box BoundingBox { get; set; }
|
public Box BoundingBox { get; set; }
|
||||||
public bool IsSelected { get; set; } = true;
|
public bool IsSelected { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>First point of the original line segments this candidate covers.</summary>
|
/// <summary>First point of the original line segments this candidate covers.</summary>
|
||||||
public Vector FirstPoint { get; set; }
|
public Vector FirstPoint { get; set; }
|
||||||
|
|
||||||
/// <summary>Last point of the original line segments this candidate covers.</summary>
|
/// <summary>Last point of the original line segments this candidate covers.</summary>
|
||||||
public Vector LastPoint { get; set; }
|
public Vector LastPoint { get; set; }
|
||||||
}
|
}
|
||||||
@@ -46,9 +48,7 @@ public class MirrorAxisResult
|
|||||||
var dx = p.X - Point.X;
|
var dx = p.X - Point.X;
|
||||||
var dy = p.Y - Point.Y;
|
var dy = p.Y - Point.Y;
|
||||||
var dot = dx * Direction.X + dy * Direction.Y;
|
var dot = dx * Direction.X + dy * Direction.Y;
|
||||||
return new Vector(
|
return new Vector(p.X - 2 * (dx - dot * Direction.X), p.Y - 2 * (dy - dot * Direction.Y));
|
||||||
p.X - 2 * (dx - dot * Direction.X),
|
|
||||||
p.Y - 2 * (dy - dot * Direction.Y));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +74,14 @@ public class GeometrySimplifier
|
|||||||
var runStart = i;
|
var runStart = i;
|
||||||
var layerName = entities[i].Layer?.Name;
|
var layerName = entities[i].Layer?.Name;
|
||||||
var lineCount = 0;
|
var lineCount = 0;
|
||||||
while (i < entities.Count && (entities[i] is Line || entities[i] is Arc) && entities[i].Layer?.Name == layerName)
|
while (
|
||||||
|
i < entities.Count
|
||||||
|
&& (entities[i] is Line || entities[i] is Arc)
|
||||||
|
&& entities[i].Layer?.Name == layerName
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (entities[i] is Line) lineCount++;
|
if (entities[i] is Line)
|
||||||
|
lineCount++;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
var runEnd = i - 1;
|
var runEnd = i - 1;
|
||||||
@@ -90,10 +95,7 @@ public class GeometrySimplifier
|
|||||||
|
|
||||||
public Shape Apply(Shape shape, List<ArcCandidate> candidates)
|
public Shape Apply(Shape shape, List<ArcCandidate> candidates)
|
||||||
{
|
{
|
||||||
var selected = candidates
|
var selected = candidates.Where(c => c.IsSelected).OrderBy(c => c.StartIndex).ToList();
|
||||||
.Where(c => c.IsSelected)
|
|
||||||
.OrderBy(c => c.StartIndex)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var newEntities = new List<Entity>();
|
var newEntities = new List<Entity>();
|
||||||
var i = 0;
|
var i = 0;
|
||||||
@@ -132,11 +134,10 @@ public class GeometrySimplifier
|
|||||||
foreach (var e in shape.Entities)
|
foreach (var e in shape.Entities)
|
||||||
midpoints.Add(e.BoundingBox.Center);
|
midpoints.Add(e.BoundingBox.Center);
|
||||||
|
|
||||||
if (midpoints.Count < 4) return MirrorAxisResult.None;
|
if (midpoints.Count < 4)
|
||||||
|
return MirrorAxisResult.None;
|
||||||
|
|
||||||
var centroid = new Vector(
|
var centroid = new Vector(midpoints.Average(p => p.X), midpoints.Average(p => p.Y));
|
||||||
midpoints.Average(p => p.X),
|
|
||||||
midpoints.Average(p => p.Y));
|
|
||||||
var cx = centroid.X;
|
var cx = centroid.X;
|
||||||
var cy = centroid.Y;
|
var cy = centroid.Y;
|
||||||
|
|
||||||
@@ -190,8 +191,7 @@ public class GeometrySimplifier
|
|||||||
return bestResult.Score >= 0.8 ? bestResult : MirrorAxisResult.None;
|
return bestResult.Score >= 0.8 ? bestResult : MirrorAxisResult.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double NormalizeAngle(double angle) =>
|
private static double NormalizeAngle(double angle) => angle < 0 ? angle + Angle.TwoPI : angle;
|
||||||
angle < 0 ? angle + Angle.TwoPI : angle;
|
|
||||||
|
|
||||||
private static Vector Normalize(Vector v)
|
private static Vector Normalize(Vector v)
|
||||||
{
|
{
|
||||||
@@ -231,7 +231,8 @@ public class GeometrySimplifier
|
|||||||
|
|
||||||
for (var j = 0; j < points.Count; j++)
|
for (var j = 0; j < points.Count; j++)
|
||||||
{
|
{
|
||||||
if (i == j) continue;
|
if (i == j)
|
||||||
|
continue;
|
||||||
var d = reflected.DistanceTo(points[j]);
|
var d = reflected.DistanceTo(points[j]);
|
||||||
if (d < matchTol)
|
if (d < matchTol)
|
||||||
{
|
{
|
||||||
@@ -251,17 +252,20 @@ public class GeometrySimplifier
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Symmetrize(List<ArcCandidate> candidates, MirrorAxisResult axis)
|
public void Symmetrize(List<ArcCandidate> candidates, MirrorAxisResult axis)
|
||||||
{
|
{
|
||||||
if (!axis.IsValid || candidates.Count < 2) return;
|
if (!axis.IsValid || candidates.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
var paired = new HashSet<int>();
|
var paired = new HashSet<int>();
|
||||||
|
|
||||||
for (var i = 0; i < candidates.Count; i++)
|
for (var i = 0; i < candidates.Count; i++)
|
||||||
{
|
{
|
||||||
if (paired.Contains(i)) continue;
|
if (paired.Contains(i))
|
||||||
|
continue;
|
||||||
|
|
||||||
var ci = candidates[i];
|
var ci = candidates[i];
|
||||||
var ciCenter = ci.BoundingBox.Center;
|
var ciCenter = ci.BoundingBox.Center;
|
||||||
if (PerpendicularDistance(ciCenter, axis.Point, axis.Direction) < 0.1) continue; // on the axis
|
if (PerpendicularDistance(ciCenter, axis.Point, axis.Direction) < 0.1)
|
||||||
|
continue; // on the axis
|
||||||
|
|
||||||
var mirrorCenter = axis.Reflect(ciCenter);
|
var mirrorCenter = axis.Reflect(ciCenter);
|
||||||
|
|
||||||
@@ -269,7 +273,8 @@ public class GeometrySimplifier
|
|||||||
var bestDist = double.MaxValue;
|
var bestDist = double.MaxValue;
|
||||||
for (var j = i + 1; j < candidates.Count; j++)
|
for (var j = i + 1; j < candidates.Count; j++)
|
||||||
{
|
{
|
||||||
if (paired.Contains(j)) continue;
|
if (paired.Contains(j))
|
||||||
|
continue;
|
||||||
var d = mirrorCenter.DistanceTo(candidates[j].BoundingBox.Center);
|
var d = mirrorCenter.DistanceTo(candidates[j].BoundingBox.Center);
|
||||||
if (d < bestDist)
|
if (d < bestDist)
|
||||||
{
|
{
|
||||||
@@ -279,7 +284,8 @@ public class GeometrySimplifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
var matchTol = System.Math.Max(ci.BoundingBox.Width, ci.BoundingBox.Length) * 0.5;
|
var matchTol = System.Math.Max(ci.BoundingBox.Width, ci.BoundingBox.Length) * 0.5;
|
||||||
if (bestJ < 0 || bestDist > matchTol) continue;
|
if (bestJ < 0 || bestDist > matchTol)
|
||||||
|
continue;
|
||||||
|
|
||||||
paired.Add(i);
|
paired.Add(i);
|
||||||
paired.Add(bestJ);
|
paired.Add(bestJ);
|
||||||
@@ -287,7 +293,10 @@ public class GeometrySimplifier
|
|||||||
var cj = candidates[bestJ];
|
var cj = candidates[bestJ];
|
||||||
var sourceIdx = i;
|
var sourceIdx = i;
|
||||||
var targetIdx = bestJ;
|
var targetIdx = bestJ;
|
||||||
if (cj.LineCount > ci.LineCount || (cj.LineCount == ci.LineCount && cj.MaxDeviation < ci.MaxDeviation))
|
if (
|
||||||
|
cj.LineCount > ci.LineCount
|
||||||
|
|| (cj.LineCount == ci.LineCount && cj.MaxDeviation < ci.MaxDeviation)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
sourceIdx = bestJ;
|
sourceIdx = bestJ;
|
||||||
targetIdx = i;
|
targetIdx = i;
|
||||||
@@ -323,8 +332,12 @@ public class GeometrySimplifier
|
|||||||
var mirrorEp = axis.Reflect(ep);
|
var mirrorEp = axis.Reflect(ep);
|
||||||
|
|
||||||
// Mirroring reverses winding — swap start/end to preserve arc direction
|
// Mirroring reverses winding — swap start/end to preserve arc direction
|
||||||
var mirrorStart = NormalizeAngle(System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X));
|
var mirrorStart = NormalizeAngle(
|
||||||
var mirrorEnd = NormalizeAngle(System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X));
|
System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X)
|
||||||
|
);
|
||||||
|
var mirrorEnd = NormalizeAngle(
|
||||||
|
System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X)
|
||||||
|
);
|
||||||
|
|
||||||
var result = new Arc(mirrorCenter, arc.Radius, mirrorStart, mirrorEnd, arc.IsReversed);
|
var result = new Arc(mirrorCenter, arc.Radius, mirrorStart, mirrorEnd, arc.IsReversed);
|
||||||
result.Layer = arc.Layer;
|
result.Layer = arc.Layer;
|
||||||
@@ -332,7 +345,12 @@ public class GeometrySimplifier
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FindCandidatesInRun(List<Entity> entities, int runStart, int runEnd, List<ArcCandidate> candidates)
|
private void FindCandidatesInRun(
|
||||||
|
List<Entity> entities,
|
||||||
|
int runStart,
|
||||||
|
int runEnd,
|
||||||
|
List<ArcCandidate> candidates
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var j = runStart;
|
var j = runStart;
|
||||||
var chainedTangent = Vector.Invalid;
|
var chainedTangent = Vector.Invalid;
|
||||||
@@ -349,7 +367,8 @@ public class GeometrySimplifier
|
|||||||
|
|
||||||
chainedTangent = ComputeEndTangent(result.Center, result.Points);
|
chainedTangent = ComputeEndTangent(result.Center, result.Points);
|
||||||
var arc = CreateArc(result.Center, result.Radius, result.Points, entities[j]);
|
var arc = CreateArc(result.Center, result.Radius, result.Points, entities[j]);
|
||||||
candidates.Add(new ArcCandidate
|
candidates.Add(
|
||||||
|
new ArcCandidate
|
||||||
{
|
{
|
||||||
StartIndex = j,
|
StartIndex = j,
|
||||||
EndIndex = result.EndIndex,
|
EndIndex = result.EndIndex,
|
||||||
@@ -358,36 +377,53 @@ public class GeometrySimplifier
|
|||||||
BoundingBox = result.Points.GetBoundingBox(),
|
BoundingBox = result.Points.GetBoundingBox(),
|
||||||
FirstPoint = arc.StartPoint(),
|
FirstPoint = arc.StartPoint(),
|
||||||
LastPoint = arc.EndPoint(),
|
LastPoint = arc.EndPoint(),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
j = result.EndIndex + 1;
|
j = result.EndIndex + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record ArcFitResult(Vector Center, double Radius, double Deviation, List<Vector> Points, int EndIndex);
|
private record ArcFitResult(
|
||||||
|
Vector Center,
|
||||||
|
double Radius,
|
||||||
|
double Deviation,
|
||||||
|
List<Vector> Points,
|
||||||
|
int EndIndex
|
||||||
|
);
|
||||||
|
|
||||||
private ArcFitResult TryFitArcAt(List<Entity> entities, int start, int runEnd, Vector chainedTangent)
|
private ArcFitResult TryFitArcAt(
|
||||||
|
List<Entity> entities,
|
||||||
|
int start,
|
||||||
|
int runEnd,
|
||||||
|
Vector chainedTangent
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var k = start + MinLines - 1;
|
var k = start + MinLines - 1;
|
||||||
if (k > runEnd) return null;
|
if (k > runEnd)
|
||||||
|
return null;
|
||||||
|
|
||||||
var points = CollectPoints(entities, start, k);
|
var points = CollectPoints(entities, start, k);
|
||||||
if (points.Count < 3) return null;
|
if (points.Count < 3)
|
||||||
|
return null;
|
||||||
|
|
||||||
var startTangent = EstimateStartTangent(entities, start, points, chainedTangent);
|
var startTangent = EstimateStartTangent(entities, start, points, chainedTangent);
|
||||||
var endTangent = EstimateEndTangent(entities, k, points);
|
var endTangent = EstimateEndTangent(entities, k, points);
|
||||||
var (center, radius, dev) = TryFit(points, startTangent, endTangent);
|
var (center, radius, dev) = TryFit(points, startTangent, endTangent);
|
||||||
if (!center.IsValid()) return null;
|
if (!center.IsValid())
|
||||||
|
return null;
|
||||||
|
|
||||||
// Extend the arc as far as possible
|
// Extend the arc as far as possible
|
||||||
while (k + 1 <= runEnd)
|
while (k + 1 <= runEnd)
|
||||||
{
|
{
|
||||||
var extPoints = CollectPoints(entities, start, k + 1);
|
var extPoints = CollectPoints(entities, start, k + 1);
|
||||||
if (extPoints.Count < 3) break;
|
if (extPoints.Count < 3)
|
||||||
|
break;
|
||||||
|
|
||||||
var extEndTangent = EstimateEndTangent(entities, k + 1, extPoints);
|
var extEndTangent = EstimateEndTangent(entities, k + 1, extPoints);
|
||||||
var (nc, nr, nd) = TryFit(extPoints, startTangent, extEndTangent);
|
var (nc, nr, nd) = TryFit(extPoints, startTangent, extEndTangent);
|
||||||
if (!nc.IsValid()) break;
|
if (!nc.IsValid())
|
||||||
|
break;
|
||||||
|
|
||||||
k++;
|
k++;
|
||||||
center = nc;
|
center = nc;
|
||||||
@@ -407,7 +443,10 @@ public class GeometrySimplifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
private (Vector center, double radius, double deviation) TryFit(
|
private (Vector center, double radius, double deviation) TryFit(
|
||||||
List<Vector> points, TangentEstimate start, TangentEstimate end)
|
List<Vector> points,
|
||||||
|
TangentEstimate start,
|
||||||
|
TangentEstimate end
|
||||||
|
)
|
||||||
{
|
{
|
||||||
foreach (var (center, radius, dev) in FitAttempts(points, start, end))
|
foreach (var (center, radius, dev) in FitAttempts(points, start, end))
|
||||||
{
|
{
|
||||||
@@ -434,23 +473,38 @@ public class GeometrySimplifier
|
|||||||
/// passes exactly through both endpoints, so no gaps are introduced.
|
/// passes exactly through both endpoints, so no gaps are introduced.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerable<(Vector center, double radius, double deviation)> FitAttempts(
|
private IEnumerable<(Vector center, double radius, double deviation)> FitAttempts(
|
||||||
List<Vector> points, TangentEstimate start, TangentEstimate end)
|
List<Vector> points,
|
||||||
|
TangentEstimate start,
|
||||||
|
TangentEstimate end
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (start.Trusted && !end.Trusted)
|
if (start.Trusted && !end.Trusted)
|
||||||
{
|
{
|
||||||
yield return ArcFit.FitWithStartTangent(points, start.Direction);
|
yield return ArcFit.FitWithStartTangent(points, start.Direction);
|
||||||
yield return ArcFit.FitThroughEndpointsWithTangents(points, start.Direction, end.Direction);
|
yield return ArcFit.FitThroughEndpointsWithTangents(
|
||||||
|
points,
|
||||||
|
start.Direction,
|
||||||
|
end.Direction
|
||||||
|
);
|
||||||
yield return FitWithEndTangent(points, end.Direction);
|
yield return FitWithEndTangent(points, end.Direction);
|
||||||
}
|
}
|
||||||
else if (end.Trusted && !start.Trusted)
|
else if (end.Trusted && !start.Trusted)
|
||||||
{
|
{
|
||||||
yield return FitWithEndTangent(points, end.Direction);
|
yield return FitWithEndTangent(points, end.Direction);
|
||||||
yield return ArcFit.FitThroughEndpointsWithTangents(points, start.Direction, end.Direction);
|
yield return ArcFit.FitThroughEndpointsWithTangents(
|
||||||
|
points,
|
||||||
|
start.Direction,
|
||||||
|
end.Direction
|
||||||
|
);
|
||||||
yield return ArcFit.FitWithStartTangent(points, start.Direction);
|
yield return ArcFit.FitWithStartTangent(points, start.Direction);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield return ArcFit.FitThroughEndpointsWithTangents(points, start.Direction, end.Direction);
|
yield return ArcFit.FitThroughEndpointsWithTangents(
|
||||||
|
points,
|
||||||
|
start.Direction,
|
||||||
|
end.Direction
|
||||||
|
);
|
||||||
yield return ArcFit.FitWithStartTangent(points, start.Direction);
|
yield return ArcFit.FitWithStartTangent(points, start.Direction);
|
||||||
yield return FitWithEndTangent(points, end.Direction);
|
yield return FitWithEndTangent(points, end.Direction);
|
||||||
}
|
}
|
||||||
@@ -463,7 +517,9 @@ public class GeometrySimplifier
|
|||||||
/// by running the start-tangent fit on the reversed point sequence.
|
/// by running the start-tangent fit on the reversed point sequence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static (Vector center, double radius, double deviation) FitWithEndTangent(
|
private static (Vector center, double radius, double deviation) FitWithEndTangent(
|
||||||
List<Vector> points, Vector endTangent)
|
List<Vector> points,
|
||||||
|
Vector endTangent
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var reversed = new List<Vector>(points);
|
var reversed = new List<Vector>(points);
|
||||||
reversed.Reverse();
|
reversed.Reverse();
|
||||||
@@ -483,7 +539,11 @@ public class GeometrySimplifier
|
|||||||
private const double NeighborEdgeFactor = 3.0;
|
private const double NeighborEdgeFactor = 3.0;
|
||||||
|
|
||||||
private static TangentEstimate EstimateStartTangent(
|
private static TangentEstimate EstimateStartTangent(
|
||||||
List<Entity> entities, int start, List<Vector> points, Vector chainedTangent)
|
List<Entity> entities,
|
||||||
|
int start,
|
||||||
|
List<Vector> points,
|
||||||
|
Vector chainedTangent
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (chainedTangent.IsValid())
|
if (chainedTangent.IsValid())
|
||||||
return new TangentEstimate(chainedTangent, true);
|
return new TangentEstimate(chainedTangent, true);
|
||||||
@@ -495,23 +555,39 @@ public class GeometrySimplifier
|
|||||||
if (start > 0)
|
if (start > 0)
|
||||||
{
|
{
|
||||||
var prev = entities[start - 1];
|
var prev = entities[start - 1];
|
||||||
var prevEnd = prev switch { Line l => l.EndPoint, Arc a => a.EndPoint(), _ => Vector.Invalid };
|
var prevEnd = prev switch
|
||||||
|
{
|
||||||
|
Line l => l.EndPoint,
|
||||||
|
Arc a => a.EndPoint(),
|
||||||
|
_ => Vector.Invalid,
|
||||||
|
};
|
||||||
if (prevEnd.IsValid() && prevEnd.DistanceTo(points[0]) < 1e-6)
|
if (prevEnd.IsValid() && prevEnd.DistanceTo(points[0]) < 1e-6)
|
||||||
{
|
{
|
||||||
if (prev is Arc)
|
if (prev is Arc)
|
||||||
return new TangentEstimate(GetExitDirection(prev), true);
|
return new TangentEstimate(GetExitDirection(prev), true);
|
||||||
if (prev is Line prevLine && prevLine.StartPoint.DistanceTo(prevLine.EndPoint) >= NeighborEdgeFactor * firstChordLen)
|
if (
|
||||||
|
prev is Line prevLine
|
||||||
|
&& prevLine.StartPoint.DistanceTo(prevLine.EndPoint)
|
||||||
|
>= NeighborEdgeFactor * firstChordLen
|
||||||
|
)
|
||||||
return new TangentEstimate(GetExitDirection(prevLine), true);
|
return new TangentEstimate(GetExitDirection(prevLine), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var chord = new Vector(points[1].X - points[0].X, points[1].Y - points[0].Y);
|
var chord = new Vector(points[1].X - points[0].X, points[1].Y - points[0].Y);
|
||||||
if (points.Count >= 3)
|
if (points.Count >= 3)
|
||||||
return new TangentEstimate(EstimateVertexTangent(points[0], points[1], points[2], chord), false);
|
return new TangentEstimate(
|
||||||
|
EstimateVertexTangent(points[0], points[1], points[2], chord),
|
||||||
|
false
|
||||||
|
);
|
||||||
return new TangentEstimate(chord, false);
|
return new TangentEstimate(chord, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TangentEstimate EstimateEndTangent(List<Entity> entities, int k, List<Vector> points)
|
private static TangentEstimate EstimateEndTangent(
|
||||||
|
List<Entity> entities,
|
||||||
|
int k,
|
||||||
|
List<Vector> points
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (entities[k] is Arc endArc)
|
if (entities[k] is Arc endArc)
|
||||||
return new TangentEstimate(GetExitDirection(endArc), true);
|
return new TangentEstimate(GetExitDirection(endArc), true);
|
||||||
@@ -520,19 +596,31 @@ public class GeometrySimplifier
|
|||||||
if (k + 1 < entities.Count)
|
if (k + 1 < entities.Count)
|
||||||
{
|
{
|
||||||
var next = entities[k + 1];
|
var next = entities[k + 1];
|
||||||
var nextStart = next switch { Line l => l.StartPoint, Arc a => a.StartPoint(), _ => Vector.Invalid };
|
var nextStart = next switch
|
||||||
|
{
|
||||||
|
Line l => l.StartPoint,
|
||||||
|
Arc a => a.StartPoint(),
|
||||||
|
_ => Vector.Invalid,
|
||||||
|
};
|
||||||
if (nextStart.IsValid() && nextStart.DistanceTo(points[^1]) < 1e-6)
|
if (nextStart.IsValid() && nextStart.DistanceTo(points[^1]) < 1e-6)
|
||||||
{
|
{
|
||||||
if (next is Arc nextArc)
|
if (next is Arc nextArc)
|
||||||
return new TangentEstimate(GetEntryDirection(nextArc), true);
|
return new TangentEstimate(GetEntryDirection(nextArc), true);
|
||||||
if (next is Line nextLine && nextLine.StartPoint.DistanceTo(nextLine.EndPoint) >= NeighborEdgeFactor * lastChordLen)
|
if (
|
||||||
|
next is Line nextLine
|
||||||
|
&& nextLine.StartPoint.DistanceTo(nextLine.EndPoint)
|
||||||
|
>= NeighborEdgeFactor * lastChordLen
|
||||||
|
)
|
||||||
return new TangentEstimate(GetExitDirection(nextLine), true);
|
return new TangentEstimate(GetExitDirection(nextLine), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var chord = new Vector(points[^1].X - points[^2].X, points[^1].Y - points[^2].Y);
|
var chord = new Vector(points[^1].X - points[^2].X, points[^1].Y - points[^2].Y);
|
||||||
if (points.Count >= 3)
|
if (points.Count >= 3)
|
||||||
return new TangentEstimate(EstimateVertexTangent(points[^1], points[^2], points[^3], chord), false);
|
return new TangentEstimate(
|
||||||
|
EstimateVertexTangent(points[^1], points[^2], points[^3], chord),
|
||||||
|
false
|
||||||
|
);
|
||||||
return new TangentEstimate(chord, false);
|
return new TangentEstimate(chord, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,9 +651,13 @@ public class GeometrySimplifier
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the entry direction (tangent at start point) of an entity.
|
/// Returns the entry direction (tangent at start point) of an entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector GetEntryDirection(Entity entity) => entity switch
|
private static Vector GetEntryDirection(Entity entity) =>
|
||||||
|
entity switch
|
||||||
{
|
{
|
||||||
Line line => new Vector(line.EndPoint.X - line.StartPoint.X, line.EndPoint.Y - line.StartPoint.Y),
|
Line line => new Vector(
|
||||||
|
line.EndPoint.X - line.StartPoint.X,
|
||||||
|
line.EndPoint.Y - line.StartPoint.Y
|
||||||
|
),
|
||||||
Arc arc => arc.IsReversed
|
Arc arc => arc.IsReversed
|
||||||
? new Vector(System.Math.Sin(arc.StartAngle), -System.Math.Cos(arc.StartAngle))
|
? new Vector(System.Math.Sin(arc.StartAngle), -System.Math.Cos(arc.StartAngle))
|
||||||
: new Vector(-System.Math.Sin(arc.StartAngle), System.Math.Cos(arc.StartAngle)),
|
: new Vector(-System.Math.Sin(arc.StartAngle), System.Math.Cos(arc.StartAngle)),
|
||||||
@@ -622,9 +714,17 @@ public class GeometrySimplifier
|
|||||||
var dInit = (maxSagitta * maxSagitta - halfChord * halfChord) / (2 * maxSagitta);
|
var dInit = (maxSagitta * maxSagitta - halfChord * halfChord) / (2 * maxSagitta);
|
||||||
var range = System.Math.Max(System.Math.Abs(dInit) * 2, halfChord);
|
var range = System.Math.Max(System.Math.Abs(dInit) * 2, halfChord);
|
||||||
|
|
||||||
var dOpt = GoldenSectionMin(dInit - range, dInit + range,
|
var dOpt = GoldenSectionMin(
|
||||||
d => ArcFit.MaxRadialDeviation(points, mx + d * nx, my + d * ny,
|
dInit - range,
|
||||||
System.Math.Sqrt(halfChord * halfChord + d * d)));
|
dInit + range,
|
||||||
|
d =>
|
||||||
|
ArcFit.MaxRadialDeviation(
|
||||||
|
points,
|
||||||
|
mx + d * nx,
|
||||||
|
my + d * ny,
|
||||||
|
System.Math.Sqrt(halfChord * halfChord + d * d)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
var center = new Vector(mx + dOpt * nx, my + dOpt * ny);
|
var center = new Vector(mx + dOpt * nx, my + dOpt * ny);
|
||||||
var radius = System.Math.Sqrt(halfChord * halfChord + dOpt * dOpt);
|
var radius = System.Math.Sqrt(halfChord * halfChord + dOpt * dOpt);
|
||||||
@@ -676,13 +776,22 @@ public class GeometrySimplifier
|
|||||||
return points;
|
return points;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Arc CreateArc(Vector center, double radius, List<Vector> points, Entity sourceEntity)
|
private static Arc CreateArc(
|
||||||
|
Vector center,
|
||||||
|
double radius,
|
||||||
|
List<Vector> points,
|
||||||
|
Entity sourceEntity
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var firstPoint = points[0];
|
var firstPoint = points[0];
|
||||||
var lastPoint = points[^1];
|
var lastPoint = points[^1];
|
||||||
|
|
||||||
var startAngle = NormalizeAngle(System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X));
|
var startAngle = NormalizeAngle(
|
||||||
var endAngle = NormalizeAngle(System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X));
|
System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X)
|
||||||
|
);
|
||||||
|
var endAngle = NormalizeAngle(
|
||||||
|
System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X)
|
||||||
|
);
|
||||||
var isReversed = SumSignedAngles(center, points) < 0;
|
var isReversed = SumSignedAngles(center, points) < 0;
|
||||||
|
|
||||||
var arc = new Arc(center, radius, startAngle, endAngle, isReversed);
|
var arc = new Arc(center, radius, startAngle, endAngle, isReversed);
|
||||||
@@ -694,9 +803,13 @@ public class GeometrySimplifier
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the exit direction (tangent at endpoint) of an entity.
|
/// Returns the exit direction (tangent at endpoint) of an entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector GetExitDirection(Entity entity) => entity switch
|
private static Vector GetExitDirection(Entity entity) =>
|
||||||
|
entity switch
|
||||||
{
|
{
|
||||||
Line line => new Vector(line.EndPoint.X - line.StartPoint.X, line.EndPoint.Y - line.StartPoint.Y),
|
Line line => new Vector(
|
||||||
|
line.EndPoint.X - line.StartPoint.X,
|
||||||
|
line.EndPoint.Y - line.StartPoint.Y
|
||||||
|
),
|
||||||
Arc arc => arc.IsReversed
|
Arc arc => arc.IsReversed
|
||||||
? new Vector(System.Math.Sin(arc.EndAngle), -System.Math.Cos(arc.EndAngle))
|
? new Vector(System.Math.Sin(arc.EndAngle), -System.Math.Cos(arc.EndAngle))
|
||||||
: new Vector(-System.Math.Sin(arc.EndAngle), System.Math.Cos(arc.EndAngle)),
|
: new Vector(-System.Math.Sin(arc.EndAngle), System.Math.Cos(arc.EndAngle)),
|
||||||
@@ -715,8 +828,10 @@ public class GeometrySimplifier
|
|||||||
var a1 = System.Math.Atan2(points[i].Y - center.Y, points[i].X - center.X);
|
var a1 = System.Math.Atan2(points[i].Y - center.Y, points[i].X - center.X);
|
||||||
var a2 = System.Math.Atan2(points[i + 1].Y - center.Y, points[i + 1].X - center.X);
|
var a2 = System.Math.Atan2(points[i + 1].Y - center.Y, points[i + 1].X - center.X);
|
||||||
var da = a2 - a1;
|
var da = a2 - a1;
|
||||||
while (da > System.Math.PI) da -= Angle.TwoPI;
|
while (da > System.Math.PI)
|
||||||
while (da < -System.Math.PI) da += Angle.TwoPI;
|
da -= Angle.TwoPI;
|
||||||
|
while (da < -System.Math.PI)
|
||||||
|
da += Angle.TwoPI;
|
||||||
total += da;
|
total += da;
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
@@ -727,7 +842,12 @@ public class GeometrySimplifier
|
|||||||
/// back to the original line segments. This catches cases where points lie
|
/// back to the original line segments. This catches cases where points lie
|
||||||
/// on a large circle but the arc bulges far from the original straight geometry.
|
/// on a large circle but the arc bulges far from the original straight geometry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static double MaxArcToSegmentDeviation(List<Vector> points, Vector center, double radius, bool isReversed)
|
private static double MaxArcToSegmentDeviation(
|
||||||
|
List<Vector> points,
|
||||||
|
Vector center,
|
||||||
|
double radius,
|
||||||
|
bool isReversed
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var startAngle = System.Math.Atan2(points[0].Y - center.Y, points[0].X - center.X);
|
var startAngle = System.Math.Atan2(points[0].Y - center.Y, points[0].X - center.X);
|
||||||
var endAngle = System.Math.Atan2(points[^1].Y - center.Y, points[^1].X - center.X);
|
var endAngle = System.Math.Atan2(points[^1].Y - center.Y, points[^1].X - center.X);
|
||||||
@@ -735,11 +855,13 @@ public class GeometrySimplifier
|
|||||||
var sweep = endAngle - startAngle;
|
var sweep = endAngle - startAngle;
|
||||||
if (isReversed)
|
if (isReversed)
|
||||||
{
|
{
|
||||||
if (sweep > 0) sweep -= Angle.TwoPI;
|
if (sweep > 0)
|
||||||
|
sweep -= Angle.TwoPI;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (sweep < 0) sweep += Angle.TwoPI;
|
if (sweep < 0)
|
||||||
|
sweep += Angle.TwoPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sampleCount = System.Math.Max(10, (int)(System.Math.Abs(sweep) * radius * 10));
|
var sampleCount = System.Math.Max(10, (int)(System.Math.Abs(sweep) * radius * 10));
|
||||||
@@ -758,9 +880,11 @@ public class GeometrySimplifier
|
|||||||
for (var j = 0; j < points.Count - 1; j++)
|
for (var j = 0; j < points.Count - 1; j++)
|
||||||
{
|
{
|
||||||
var dist = DistanceToSegment(arcPt, points[j], points[j + 1]);
|
var dist = DistanceToSegment(arcPt, points[j], points[j + 1]);
|
||||||
if (dist < minDist) minDist = dist;
|
if (dist < minDist)
|
||||||
|
minDist = dist;
|
||||||
}
|
}
|
||||||
if (minDist > maxDev) maxDev = minDist;
|
if (minDist > maxDev)
|
||||||
|
maxDev = minDist;
|
||||||
}
|
}
|
||||||
return maxDev;
|
return maxDev;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,23 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
public int FeedMmPerSec { get; set; } = 10;
|
public int FeedMmPerSec { get; set; } = 10;
|
||||||
|
|
||||||
[DisplayName("Depth (inches)")]
|
[DisplayName("Depth (inches)")]
|
||||||
[Description("Programmed Z plunge (DZ). Note: the spring-floated spindle means this does not set actual cut depth — tool protrusion does.")]
|
[Description(
|
||||||
|
"Programmed Z plunge (DZ). Note: the spring-floated spindle means this does not set actual cut depth — tool protrusion does."
|
||||||
|
)]
|
||||||
public double Depth { get; set; } = 0.25;
|
public double Depth { get; set; } = 0.25;
|
||||||
|
|
||||||
[DisplayName("Pause Before")]
|
[DisplayName("Pause Before")]
|
||||||
[Description("Stop the spindle and prompt the operator before this pass begins, so the tool can be swapped/adjusted.")]
|
[Description(
|
||||||
|
"Stop the spindle and prompt the operator before this pass begins, so the tool can be swapped/adjusted."
|
||||||
|
)]
|
||||||
public bool PauseBefore { get; set; }
|
public bool PauseBefore { get; set; }
|
||||||
|
|
||||||
[DisplayName("Pause Message")]
|
[DisplayName("Pause Message")]
|
||||||
[Description("Message shown on the controller during the pause.")]
|
[Description("Message shown on the controller during the pause.")]
|
||||||
public string PauseMessage { get; set; } = "";
|
public string PauseMessage { get; set; } = "";
|
||||||
|
|
||||||
public override string ToString() => $"{FeedMmPerSec} mm/s, {Depth:0.###}\"" + (PauseBefore ? ", pause" : "");
|
public override string ToString() =>
|
||||||
|
$"{FeedMmPerSec} mm/s, {Depth:0.###}\"" + (PauseBefore ? ", pause" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -40,7 +45,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
[Category("Engrave (Scribe)")]
|
[Category("Engrave (Scribe)")]
|
||||||
[DisplayName("Engrave")]
|
[DisplayName("Engrave")]
|
||||||
[Description("Parameters for engrave/scribe geometry (text).")]
|
[Description("Parameters for engrave/scribe geometry (text).")]
|
||||||
public LayerCutConfig Engrave { get; set; } = new LayerCutConfig
|
public LayerCutConfig Engrave { get; set; } =
|
||||||
|
new LayerCutConfig
|
||||||
{
|
{
|
||||||
FeedMmPerSec = 10,
|
FeedMmPerSec = 10,
|
||||||
Depth = 0.25,
|
Depth = 0.25,
|
||||||
@@ -50,8 +56,11 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
|
|
||||||
[Category("Cut")]
|
[Category("Cut")]
|
||||||
[DisplayName("Cut")]
|
[DisplayName("Cut")]
|
||||||
[Description("Parameters for cut geometry (outlines). Pauses for a tool change by default.")]
|
[Description(
|
||||||
public LayerCutConfig Cut { get; set; } = new LayerCutConfig
|
"Parameters for cut geometry (outlines). Pauses for a tool change by default."
|
||||||
|
)]
|
||||||
|
public LayerCutConfig Cut { get; set; } =
|
||||||
|
new LayerCutConfig
|
||||||
{
|
{
|
||||||
FeedMmPerSec = 3,
|
FeedMmPerSec = 3,
|
||||||
Depth = 0.25,
|
Depth = 0.25,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
Converters = { new JsonStringEnumConverter() }
|
Converters = { new JsonStringEnumConverter() },
|
||||||
};
|
};
|
||||||
|
|
||||||
public string Name => "Gravograph IS8000";
|
public string Name => "Gravograph IS8000";
|
||||||
@@ -48,7 +48,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
if (File.Exists(configPath))
|
if (File.Exists(configPath))
|
||||||
{
|
{
|
||||||
var json = File.ReadAllText(configPath);
|
var json = File.ReadAllText(configPath);
|
||||||
Config = JsonSerializer.Deserialize<GravographISPostConfig>(json, JsonOptions)
|
Config =
|
||||||
|
JsonSerializer.Deserialize<GravographISPostConfig>(json, JsonOptions)
|
||||||
?? new GravographISPostConfig();
|
?? new GravographISPostConfig();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -103,14 +104,16 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyList<GravographPass> BuildPasses(IEnumerable<LayeredPolyline> polylines)
|
public IReadOnlyList<GravographPass> BuildPasses(IEnumerable<LayeredPolyline> polylines)
|
||||||
{
|
{
|
||||||
if (polylines == null) throw new ArgumentNullException(nameof(polylines));
|
if (polylines == null)
|
||||||
|
throw new ArgumentNullException(nameof(polylines));
|
||||||
|
|
||||||
var engrave = new List<IReadOnlyList<Vector>>();
|
var engrave = new List<IReadOnlyList<Vector>>();
|
||||||
var cut = new List<IReadOnlyList<Vector>>();
|
var cut = new List<IReadOnlyList<Vector>>();
|
||||||
|
|
||||||
foreach (var poly in polylines)
|
foreach (var poly in polylines)
|
||||||
{
|
{
|
||||||
if (poly == null) continue;
|
if (poly == null)
|
||||||
|
continue;
|
||||||
var block = Config.ConfigFor(poly.Layer);
|
var block = Config.ConfigFor(poly.Layer);
|
||||||
if (block == null)
|
if (block == null)
|
||||||
continue; // non-cutting (Display) geometry
|
continue; // non-cutting (Display) geometry
|
||||||
|
|||||||
@@ -48,16 +48,99 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// fixed return-to-home block.
|
// fixed return-to-home block.
|
||||||
private static readonly byte[] PreambleTemplate = new byte[]
|
private static readonly byte[] PreambleTemplate = new byte[]
|
||||||
{
|
{
|
||||||
0x21, 0x41, 0x53, 0x20, 0x33, 0x38, 0x3b, 0x01, 0x90, 0x01,
|
0x21,
|
||||||
0xf4, 0x01, 0x90, 0x01, 0xf4, 0x01, 0x90, 0x01, 0xf4, 0x00,
|
0x41,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x53,
|
||||||
0x00, 0x00, 0x09, 0x00, 0x00, 0x03, 0xe8, 0x05, 0x06, 0x00,
|
0x20,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0x32, 0x44, 0x00,
|
0x33,
|
||||||
0x00, 0xff, 0xfd, 0x4d, 0x43, 0x00, 0x01, 0xff, 0xfd, 0x4f,
|
0x38,
|
||||||
0x55, 0xff, 0xfb, 0xff, 0xfd, 0x4f, 0x55, 0xff, 0xfa, 0xff,
|
0x3b,
|
||||||
0xfd, 0x50, 0x5a, 0x00, 0x00, 0xff, 0xfd, 0x56, 0x53, 0x00,
|
0x01,
|
||||||
0x23, 0xff, 0xfd, 0x56, 0x5a, 0x00, 0x23, 0xff, 0xfd, 0x44,
|
0x90,
|
||||||
0x5a, 0x01, 0xfc,
|
0x01,
|
||||||
|
0xf4,
|
||||||
|
0x01,
|
||||||
|
0x90,
|
||||||
|
0x01,
|
||||||
|
0xf4,
|
||||||
|
0x01,
|
||||||
|
0x90,
|
||||||
|
0x01,
|
||||||
|
0xf4,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x09,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0xe8,
|
||||||
|
0x05,
|
||||||
|
0x06,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x32,
|
||||||
|
0x44,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4d,
|
||||||
|
0x43,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4f,
|
||||||
|
0x55,
|
||||||
|
0xff,
|
||||||
|
0xfb,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4f,
|
||||||
|
0x55,
|
||||||
|
0xff,
|
||||||
|
0xfa,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x50,
|
||||||
|
0x5a,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x56,
|
||||||
|
0x53,
|
||||||
|
0x00,
|
||||||
|
0x23,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x56,
|
||||||
|
0x5a,
|
||||||
|
0x00,
|
||||||
|
0x23,
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x44,
|
||||||
|
0x5a,
|
||||||
|
0x01,
|
||||||
|
0xfc,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Stripped 36-byte postamble: lift, aux off, motor off, operator beep,
|
// Stripped 36-byte postamble: lift, aux off, motor off, operator beep,
|
||||||
@@ -73,11 +156,36 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// lift + PU travel to the operator-set origin before these final commands.
|
// lift + PU travel to the operator-set origin before these final commands.
|
||||||
private static readonly byte[] EndJobBytes = new byte[]
|
private static readonly byte[] EndJobBytes = new byte[]
|
||||||
{
|
{
|
||||||
0xff, 0xfd, 0x4f, 0x55, 0xff, 0xfa, // OU 0xFFFA aux off
|
0xff,
|
||||||
0xff, 0xfd, 0x4f, 0x55, 0xff, 0xfb, // OU 0xFFFB aux off
|
0xfd,
|
||||||
0xff, 0xfd, 0x4d, 0x43, 0x00, 0x00, // MC 0x0000 motor off
|
0x4f,
|
||||||
0xff, 0xfd, 0x4f, 0x50, 0x00, 0x00, // OP 0x0000 operator beep
|
0x55,
|
||||||
0xff, 0xfd, 0x4a, 0x46, 0x00, 0x00, // JF 0x0000 job finish
|
0xff,
|
||||||
|
0xfa, // OU 0xFFFA aux off
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4f,
|
||||||
|
0x55,
|
||||||
|
0xff,
|
||||||
|
0xfb, // OU 0xFFFB aux off
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4d,
|
||||||
|
0x43,
|
||||||
|
0x00,
|
||||||
|
0x00, // MC 0x0000 motor off
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4f,
|
||||||
|
0x50,
|
||||||
|
0x00,
|
||||||
|
0x00, // OP 0x0000 operator beep
|
||||||
|
0xff,
|
||||||
|
0xfd,
|
||||||
|
0x4a,
|
||||||
|
0x46,
|
||||||
|
0x00,
|
||||||
|
0x00, // JF 0x0000 job finish
|
||||||
};
|
};
|
||||||
|
|
||||||
// 80 steps/mm × 25.4 mm/in
|
// 80 steps/mm × 25.4 mm/in
|
||||||
@@ -86,9 +194,7 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
public GravographISWriterOptions Options { get; }
|
public GravographISWriterOptions Options { get; }
|
||||||
|
|
||||||
public GravographISWriter()
|
public GravographISWriter()
|
||||||
: this(new GravographISWriterOptions())
|
: this(new GravographISWriterOptions()) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public GravographISWriter(GravographISWriterOptions options)
|
public GravographISWriter(GravographISWriterOptions options)
|
||||||
{
|
{
|
||||||
@@ -103,11 +209,13 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Write(IEnumerable<IReadOnlyList<Vector>> polylines, Stream output)
|
public void Write(IEnumerable<IReadOnlyList<Vector>> polylines, Stream output)
|
||||||
{
|
{
|
||||||
if (polylines == null) throw new ArgumentNullException(nameof(polylines));
|
if (polylines == null)
|
||||||
|
throw new ArgumentNullException(nameof(polylines));
|
||||||
|
|
||||||
// A single pass at the configured feed/depth — byte-identical to the
|
// A single pass at the configured feed/depth — byte-identical to the
|
||||||
// original single-group output (no transitions, no pause).
|
// original single-group output (no transitions, no pause).
|
||||||
Write(new[]
|
Write(
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
new GravographPass
|
new GravographPass
|
||||||
{
|
{
|
||||||
@@ -117,7 +225,9 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
PauseBefore = false,
|
PauseBefore = false,
|
||||||
PauseMessage = "",
|
PauseMessage = "",
|
||||||
},
|
},
|
||||||
}, output);
|
},
|
||||||
|
output
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -128,8 +238,10 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Write(IReadOnlyList<GravographPass> passes, Stream output)
|
public void Write(IReadOnlyList<GravographPass> passes, Stream output)
|
||||||
{
|
{
|
||||||
if (passes == null) throw new ArgumentNullException(nameof(passes));
|
if (passes == null)
|
||||||
if (output == null) throw new ArgumentNullException(nameof(output));
|
throw new ArgumentNullException(nameof(passes));
|
||||||
|
if (output == null)
|
||||||
|
throw new ArgumentNullException(nameof(output));
|
||||||
|
|
||||||
var firstFeed = passes.Count > 0 ? passes[0].FeedMmPerSec : Options.FeedMmPerSec;
|
var firstFeed = passes.Count > 0 ? passes[0].FeedMmPerSec : Options.FeedMmPerSec;
|
||||||
var firstDepth = passes.Count > 0 ? passes[0].DepthInches : Options.DepthInches;
|
var firstDepth = passes.Count > 0 ? passes[0].DepthInches : Options.DepthInches;
|
||||||
@@ -146,10 +258,16 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// catch bad records before they ship to the engraver.
|
// catch bad records before they ship to the engraver.
|
||||||
var headX = 0;
|
var headX = 0;
|
||||||
var headY = 0;
|
var headY = 0;
|
||||||
var envelopeXSteps = (int)System.Math.Round(Options.WorkEnvelopeXMm * StepsPerMm,
|
var envelopeXSteps = (int)
|
||||||
MidpointRounding.AwayFromZero);
|
System.Math.Round(
|
||||||
var envelopeYSteps = (int)System.Math.Round(Options.WorkEnvelopeYMm * StepsPerMm,
|
Options.WorkEnvelopeXMm * StepsPerMm,
|
||||||
MidpointRounding.AwayFromZero);
|
MidpointRounding.AwayFromZero
|
||||||
|
);
|
||||||
|
var envelopeYSteps = (int)
|
||||||
|
System.Math.Round(
|
||||||
|
Options.WorkEnvelopeYMm * StepsPerMm,
|
||||||
|
MidpointRounding.AwayFromZero
|
||||||
|
);
|
||||||
|
|
||||||
var firstPolyline = true;
|
var firstPolyline = true;
|
||||||
var polyIndex = 0;
|
var polyIndex = 0;
|
||||||
@@ -167,9 +285,18 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// Park: lift Z, then rapid (pen-up) back to the operator origin so
|
// Park: lift Z, then rapid (pen-up) back to the operator origin so
|
||||||
// the head is clear of the work while the tool is swapped.
|
// the head is clear of the work while the tool is swapped.
|
||||||
WriteLiftOnly(output);
|
WriteLiftOnly(output);
|
||||||
WriteTravel(output, (byte)'P', (byte)'U',
|
WriteTravel(
|
||||||
checked(-headX), checked(-headY),
|
output,
|
||||||
ref headX, ref headY, envelopeXSteps, envelopeYSteps, polyIndex);
|
(byte)'P',
|
||||||
|
(byte)'U',
|
||||||
|
checked(-headX),
|
||||||
|
checked(-headY),
|
||||||
|
ref headX,
|
||||||
|
ref headY,
|
||||||
|
envelopeXSteps,
|
||||||
|
envelopeYSteps,
|
||||||
|
polyIndex
|
||||||
|
);
|
||||||
WritePauseCore(output, pass.PauseMessage);
|
WritePauseCore(output, pass.PauseMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,12 +309,18 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
|
|
||||||
if (pass.DepthInches != currentDepth)
|
if (pass.DepthInches != currentDepth)
|
||||||
{
|
{
|
||||||
WriteCommand(output, (byte)'D', (byte)'Z', DepthInStepsAsInt16(pass.DepthInches));
|
WriteCommand(
|
||||||
|
output,
|
||||||
|
(byte)'D',
|
||||||
|
(byte)'Z',
|
||||||
|
DepthInStepsAsInt16(pass.DepthInches)
|
||||||
|
);
|
||||||
currentDepth = pass.DepthInches;
|
currentDepth = pass.DepthInches;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pass.Polylines == null) continue;
|
if (pass.Polylines == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
foreach (var poly in pass.Polylines)
|
foreach (var poly in pass.Polylines)
|
||||||
{
|
{
|
||||||
@@ -195,31 +328,62 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
if (poly == null || poly.Count < 2)
|
if (poly == null || poly.Count < 2)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
WritePolyline(output, poly, ref firstPolyline, ref headX, ref headY,
|
WritePolyline(
|
||||||
envelopeXSteps, envelopeYSteps, polyIndex);
|
output,
|
||||||
|
poly,
|
||||||
|
ref firstPolyline,
|
||||||
|
ref headX,
|
||||||
|
ref headY,
|
||||||
|
envelopeXSteps,
|
||||||
|
envelopeYSteps,
|
||||||
|
polyIndex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLiftOnly(output);
|
WriteLiftOnly(output);
|
||||||
if (Options.ReturnToOriginAtEnd && !firstPolyline)
|
if (Options.ReturnToOriginAtEnd && !firstPolyline)
|
||||||
{
|
{
|
||||||
WriteTravel(output, (byte)'P', (byte)'U',
|
WriteTravel(
|
||||||
checked(-headX), checked(-headY),
|
output,
|
||||||
ref headX, ref headY, envelopeXSteps, envelopeYSteps, polyIndex);
|
(byte)'P',
|
||||||
|
(byte)'U',
|
||||||
|
checked(-headX),
|
||||||
|
checked(-headY),
|
||||||
|
ref headX,
|
||||||
|
ref headY,
|
||||||
|
envelopeXSteps,
|
||||||
|
envelopeYSteps,
|
||||||
|
polyIndex
|
||||||
|
);
|
||||||
}
|
}
|
||||||
output.Write(EndJobBytes, 0, EndJobBytes.Length);
|
output.Write(EndJobBytes, 0, EndJobBytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WritePolyline(Stream output, IReadOnlyList<Vector> poly,
|
private void WritePolyline(
|
||||||
ref bool firstPolyline, ref int headX, ref int headY,
|
Stream output,
|
||||||
int envelopeXSteps, int envelopeYSteps, int polyIndex)
|
IReadOnlyList<Vector> poly,
|
||||||
|
ref bool firstPolyline,
|
||||||
|
ref int headX,
|
||||||
|
ref int headY,
|
||||||
|
int envelopeXSteps,
|
||||||
|
int envelopeYSteps,
|
||||||
|
int polyIndex
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var (startX, startY) = ToWire(poly[0]);
|
var (startX, startY) = ToWire(poly[0]);
|
||||||
WriteTravel(output,
|
WriteTravel(
|
||||||
|
output,
|
||||||
firstPolyline ? (byte)'D' : (byte)'P',
|
firstPolyline ? (byte)'D' : (byte)'P',
|
||||||
firstPolyline ? (byte)'R' : (byte)'U',
|
firstPolyline ? (byte)'R' : (byte)'U',
|
||||||
checked(startX - headX), checked(startY - headY),
|
checked(startX - headX),
|
||||||
ref headX, ref headY, envelopeXSteps, envelopeYSteps, polyIndex);
|
checked(startY - headY),
|
||||||
|
ref headX,
|
||||||
|
ref headY,
|
||||||
|
envelopeXSteps,
|
||||||
|
envelopeYSteps,
|
||||||
|
polyIndex
|
||||||
|
);
|
||||||
|
|
||||||
// PD command + single records-follow flag, then one record per segment.
|
// PD command + single records-follow flag, then one record per segment.
|
||||||
output.WriteByte(0xFF);
|
output.WriteByte(0xFF);
|
||||||
@@ -236,8 +400,15 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
var (cx, cy) = ToWire(poly[i]);
|
var (cx, cy) = ToWire(poly[i]);
|
||||||
var dx = checked(cx - prevX);
|
var dx = checked(cx - prevX);
|
||||||
var dy = checked(cy - prevY);
|
var dy = checked(cy - prevY);
|
||||||
EnsureEnvelope(headX + dx, headY + dy, envelopeXSteps, envelopeYSteps,
|
EnsureEnvelope(
|
||||||
polyIndex, segment: i, isTravel: false);
|
headX + dx,
|
||||||
|
headY + dy,
|
||||||
|
envelopeXSteps,
|
||||||
|
envelopeYSteps,
|
||||||
|
polyIndex,
|
||||||
|
segment: i,
|
||||||
|
isTravel: false
|
||||||
|
);
|
||||||
WriteRecord(output, dx, dy);
|
WriteRecord(output, dx, dy);
|
||||||
prevX = cx;
|
prevX = cx;
|
||||||
prevY = cy;
|
prevY = cy;
|
||||||
@@ -267,7 +438,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// space-padded to a whole number of packets so widths stay 2 bytes.
|
// space-padded to a whole number of packets so widths stay 2 bytes.
|
||||||
private static void WriteMessagePackets(Stream s, string message)
|
private static void WriteMessagePackets(Stream s, string message)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(message)) return;
|
if (string.IsNullOrEmpty(message))
|
||||||
|
return;
|
||||||
|
|
||||||
var chars = Encoding.ASCII.GetBytes(message);
|
var chars = Encoding.ASCII.GetBytes(message);
|
||||||
for (var i = 0; i < chars.Length; i += 2)
|
for (var i = 0; i < chars.Length; i += 2)
|
||||||
@@ -295,11 +467,18 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
|
|
||||||
private const double StepsPerMm = 80.0;
|
private const double StepsPerMm = 80.0;
|
||||||
|
|
||||||
private void EnsureEnvelope(int wireX, int wireY,
|
private void EnsureEnvelope(
|
||||||
int envXSteps, int envYSteps,
|
int wireX,
|
||||||
int polyIndex, int segment, bool isTravel)
|
int wireY,
|
||||||
|
int envXSteps,
|
||||||
|
int envYSteps,
|
||||||
|
int polyIndex,
|
||||||
|
int segment,
|
||||||
|
bool isTravel
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (!Options.EnvelopeGuardEnabled) return;
|
if (!Options.EnvelopeGuardEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
// Wire frame: X is identity to input; Y is negated. With the operator
|
// Wire frame: X is identity to input; Y is negated. With the operator
|
||||||
// origin set at the upper-left of the work envelope and an OpenNest
|
// origin set at the upper-left of the work envelope and an OpenNest
|
||||||
@@ -313,16 +492,21 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
var inputY = -wireY / (double)StepsPerInch;
|
var inputY = -wireY / (double)StepsPerInch;
|
||||||
var kind = isTravel ? "pen-up travel" : "cut segment";
|
var kind = isTravel ? "pen-up travel" : "cut segment";
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"Polyline {polyIndex} {kind} (segment {segment}) would place the head at " +
|
$"Polyline {polyIndex} {kind} (segment {segment}) would place the head at "
|
||||||
$"({inputX:F3}\", {inputY:F3}\"), outside the {Options.WorkEnvelopeXMm}×{Options.WorkEnvelopeYMm} mm " +
|
+ $"({inputX:F3}\", {inputY:F3}\"), outside the {Options.WorkEnvelopeXMm}×{Options.WorkEnvelopeYMm} mm "
|
||||||
$"work envelope from upper-left origin. Refusing to emit the record.");
|
+ $"work envelope from upper-left origin. Refusing to emit the record."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static short DepthInStepsAsInt16(double depthInches)
|
private static short DepthInStepsAsInt16(double depthInches)
|
||||||
{
|
{
|
||||||
var steps = (long)System.Math.Round(depthInches * StepsPerInch, MidpointRounding.AwayFromZero);
|
var steps = (long)
|
||||||
|
System.Math.Round(depthInches * StepsPerInch, MidpointRounding.AwayFromZero);
|
||||||
if (steps < short.MinValue || steps > short.MaxValue)
|
if (steps < short.MinValue || steps > short.MaxValue)
|
||||||
throw new ArgumentOutOfRangeException(nameof(depthInches), $"Depth {depthInches} in. → {steps} steps overflows int16.");
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(depthInches),
|
||||||
|
$"Depth {depthInches} in. → {steps} steps overflows int16."
|
||||||
|
);
|
||||||
return (short)steps;
|
return (short)steps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,10 +519,18 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
return (x, y);
|
return (x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteTravel(Stream s, byte c0, byte c1, int dx, int dy,
|
private void WriteTravel(
|
||||||
ref int headX, ref int headY,
|
Stream s,
|
||||||
int envelopeXSteps, int envelopeYSteps,
|
byte c0,
|
||||||
int polyIndex)
|
byte c1,
|
||||||
|
int dx,
|
||||||
|
int dy,
|
||||||
|
ref int headX,
|
||||||
|
ref int headY,
|
||||||
|
int envelopeXSteps,
|
||||||
|
int envelopeYSteps,
|
||||||
|
int polyIndex
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (dx == 0 && dy == 0)
|
if (dx == 0 && dy == 0)
|
||||||
return;
|
return;
|
||||||
@@ -352,20 +544,31 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
|
|
||||||
var chunks = System.Math.Max(
|
var chunks = System.Math.Max(
|
||||||
(int)System.Math.Ceiling(System.Math.Abs(dx) / (double)short.MaxValue),
|
(int)System.Math.Ceiling(System.Math.Abs(dx) / (double)short.MaxValue),
|
||||||
(int)System.Math.Ceiling(System.Math.Abs(dy) / (double)short.MaxValue));
|
(int)System.Math.Ceiling(System.Math.Abs(dy) / (double)short.MaxValue)
|
||||||
if (chunks < 1) chunks = 1;
|
);
|
||||||
|
if (chunks < 1)
|
||||||
|
chunks = 1;
|
||||||
|
|
||||||
var emittedX = 0;
|
var emittedX = 0;
|
||||||
var emittedY = 0;
|
var emittedY = 0;
|
||||||
for (var i = 1; i <= chunks; i++)
|
for (var i = 1; i <= chunks; i++)
|
||||||
{
|
{
|
||||||
var targetX = (int)System.Math.Round(dx * (i / (double)chunks), MidpointRounding.AwayFromZero);
|
var targetX = (int)
|
||||||
var targetY = (int)System.Math.Round(dy * (i / (double)chunks), MidpointRounding.AwayFromZero);
|
System.Math.Round(dx * (i / (double)chunks), MidpointRounding.AwayFromZero);
|
||||||
|
var targetY = (int)
|
||||||
|
System.Math.Round(dy * (i / (double)chunks), MidpointRounding.AwayFromZero);
|
||||||
var chunkX = checked(targetX - emittedX);
|
var chunkX = checked(targetX - emittedX);
|
||||||
var chunkY = checked(targetY - emittedY);
|
var chunkY = checked(targetY - emittedY);
|
||||||
|
|
||||||
EnsureEnvelope(headX + chunkX, headY + chunkY, envelopeXSteps, envelopeYSteps,
|
EnsureEnvelope(
|
||||||
polyIndex, segment: 0, isTravel: true);
|
headX + chunkX,
|
||||||
|
headY + chunkY,
|
||||||
|
envelopeXSteps,
|
||||||
|
envelopeYSteps,
|
||||||
|
polyIndex,
|
||||||
|
segment: 0,
|
||||||
|
isTravel: true
|
||||||
|
);
|
||||||
WriteRecord(s, chunkX, chunkY);
|
WriteRecord(s, chunkX, chunkY);
|
||||||
|
|
||||||
emittedX = targetX;
|
emittedX = targetX;
|
||||||
@@ -399,11 +602,16 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
|
|
||||||
private static void WriteRecord(Stream s, int dx, int dy)
|
private static void WriteRecord(Stream s, int dx, int dy)
|
||||||
{
|
{
|
||||||
if (dx < short.MinValue || dx > short.MaxValue ||
|
if (
|
||||||
dy < short.MinValue || dy > short.MaxValue)
|
dx < short.MinValue
|
||||||
|
|| dx > short.MaxValue
|
||||||
|
|| dy < short.MinValue
|
||||||
|
|| dy > short.MaxValue
|
||||||
|
)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"Move delta ({dx}, {dy}) steps overflows signed int16 — split moves upstream.");
|
$"Move delta ({dx}, {dy}) steps overflows signed int16 — split moves upstream."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int word1;
|
int word1;
|
||||||
@@ -423,11 +631,15 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var maxAbs = System.Math.Max(absDx, absDy);
|
var maxAbs = System.Math.Max(absDx, absDy);
|
||||||
word1 = (int)System.Math.Round(16384.0 * maxAbs / len, MidpointRounding.AwayFromZero);
|
word1 = (int)
|
||||||
|
System.Math.Round(16384.0 * maxAbs / len, MidpointRounding.AwayFromZero);
|
||||||
param = (int)System.Math.Round(len / 22.4, MidpointRounding.AwayFromZero);
|
param = (int)System.Math.Round(len / 22.4, MidpointRounding.AwayFromZero);
|
||||||
if (param < 1) param = 1;
|
if (param < 1)
|
||||||
if (param > 180) param = 180;
|
param = 1;
|
||||||
if (word1 > 16384) word1 = 16384;
|
if (param > 180)
|
||||||
|
param = 180;
|
||||||
|
if (word1 > 16384)
|
||||||
|
word1 = 16384;
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteBigEndianInt16(s, (short)word1);
|
WriteBigEndianInt16(s, (short)word1);
|
||||||
@@ -448,8 +660,12 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
{
|
{
|
||||||
for (int i = 0; i <= buffer.Length - 6; i++)
|
for (int i = 0; i <= buffer.Length - 6; i++)
|
||||||
{
|
{
|
||||||
if (buffer[i] == 0xFF && buffer[i + 1] == 0xFD &&
|
if (
|
||||||
buffer[i + 2] == c0 && buffer[i + 3] == c1)
|
buffer[i] == 0xFF
|
||||||
|
&& buffer[i + 1] == 0xFD
|
||||||
|
&& buffer[i + 2] == c0
|
||||||
|
&& buffer[i + 3] == c1
|
||||||
|
)
|
||||||
{
|
{
|
||||||
buffer[i + 4] = (byte)((value >> 8) & 0xFF);
|
buffer[i + 4] = (byte)((value >> 8) & 0xFF);
|
||||||
buffer[i + 5] = (byte)(value & 0xFF);
|
buffer[i + 5] = (byte)(value & 0xFF);
|
||||||
@@ -458,7 +674,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
$"Command '{(char)c0}{(char)c1}' not found in preamble template.");
|
$"Command '{(char)c0}{(char)c1}' not found in preamble template."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<LayeredPolyline> ExtractLayered(Nest nest)
|
public List<LayeredPolyline> ExtractLayered(Nest nest)
|
||||||
{
|
{
|
||||||
if (nest == null) throw new ArgumentNullException(nameof(nest));
|
if (nest == null)
|
||||||
|
throw new ArgumentNullException(nameof(nest));
|
||||||
|
|
||||||
var result = new List<LayeredPolyline>();
|
var result = new List<LayeredPolyline>();
|
||||||
|
|
||||||
@@ -91,7 +92,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
private void ExtractPart(Part part, List<LayeredPolyline> sink)
|
private void ExtractPart(Part part, List<LayeredPolyline> sink)
|
||||||
{
|
{
|
||||||
var program = part.Program;
|
var program = part.Program;
|
||||||
if (program == null) return;
|
if (program == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// The walk below treats Motion.EndPoint as absolute. Convert a working
|
// The walk below treats Motion.EndPoint as absolute. Convert a working
|
||||||
// copy to absolute mode so G91 programs (the form OpenNest's UI writes)
|
// copy to absolute mode so G91 programs (the form OpenNest's UI writes)
|
||||||
@@ -123,7 +125,13 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
|
|
||||||
case LinearMove linear:
|
case LinearMove linear:
|
||||||
{
|
{
|
||||||
StartOrSplit(sink, ref current, ref currentLayer, linear.Layer, pos + offset);
|
StartOrSplit(
|
||||||
|
sink,
|
||||||
|
ref current,
|
||||||
|
ref currentLayer,
|
||||||
|
linear.Layer,
|
||||||
|
pos + offset
|
||||||
|
);
|
||||||
var end = linear.EndPoint;
|
var end = linear.EndPoint;
|
||||||
current.Add(end + offset);
|
current.Add(end + offset);
|
||||||
pos = end;
|
pos = end;
|
||||||
@@ -147,8 +155,13 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// seeded at `seed` (the current pen position). When the layer changes
|
// seeded at `seed` (the current pen position). When the layer changes
|
||||||
// mid-chain the previous polyline is flushed and a new one begins at the
|
// mid-chain the previous polyline is flushed and a new one begins at the
|
||||||
// shared seam vertex so engrave and cut passes stay geometrically continuous.
|
// shared seam vertex so engrave and cut passes stay geometrically continuous.
|
||||||
private static void StartOrSplit(List<LayeredPolyline> sink, ref List<Vector> current,
|
private static void StartOrSplit(
|
||||||
ref LayerType currentLayer, LayerType moveLayer, Vector seed)
|
List<LayeredPolyline> sink,
|
||||||
|
ref List<Vector> current,
|
||||||
|
ref LayerType currentLayer,
|
||||||
|
LayerType moveLayer,
|
||||||
|
Vector seed
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (current == null)
|
if (current == null)
|
||||||
{
|
{
|
||||||
@@ -163,7 +176,11 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FlushCurrent(List<LayeredPolyline> sink, ref List<Vector> current, LayerType layer)
|
private static void FlushCurrent(
|
||||||
|
List<LayeredPolyline> sink,
|
||||||
|
ref List<Vector> current,
|
||||||
|
LayerType layer
|
||||||
|
)
|
||||||
{
|
{
|
||||||
if (current != null && current.Count >= 2)
|
if (current != null && current.Count >= 2)
|
||||||
sink.Add(new LayeredPolyline(current, layer));
|
sink.Add(new LayeredPolyline(current, layer));
|
||||||
@@ -175,8 +192,13 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
// (G-code I/J in this codebase are stored as the absolute center), arc.EndPoint
|
// (G-code I/J in this codebase are stored as the absolute center), arc.EndPoint
|
||||||
// is absolute end. The starting point is assumed to already be in the polyline;
|
// is absolute end. The starting point is assumed to already be in the polyline;
|
||||||
// intermediate samples and the endpoint are appended.
|
// intermediate samples and the endpoint are appended.
|
||||||
private static void TessellateArc(Vector start, ArcMove arc, Vector offset,
|
private static void TessellateArc(
|
||||||
double chordTol, List<Vector> sink)
|
Vector start,
|
||||||
|
ArcMove arc,
|
||||||
|
Vector offset,
|
||||||
|
double chordTol,
|
||||||
|
List<Vector> sink
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var c = arc.CenterPoint;
|
var c = arc.CenterPoint;
|
||||||
var r = c.DistanceTo(start);
|
var r = c.DistanceTo(start);
|
||||||
@@ -193,18 +215,22 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
if (arc.Rotation == RotationType.CW)
|
if (arc.Rotation == RotationType.CW)
|
||||||
{
|
{
|
||||||
sweep = a0 - a1;
|
sweep = a0 - a1;
|
||||||
if (sweep <= 0) sweep += 2 * System.Math.PI;
|
if (sweep <= 0)
|
||||||
|
sweep += 2 * System.Math.PI;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sweep = a1 - a0;
|
sweep = a1 - a0;
|
||||||
if (sweep <= 0) sweep += 2 * System.Math.PI;
|
if (sweep <= 0)
|
||||||
|
sweep += 2 * System.Math.PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Treat a near-zero sweep with coincident start/end as a full circle.
|
// Treat a near-zero sweep with coincident start/end as a full circle.
|
||||||
if (sweep < 1e-9 &&
|
if (
|
||||||
System.Math.Abs(start.X - arc.EndPoint.X) < 1e-9 &&
|
sweep < 1e-9
|
||||||
System.Math.Abs(start.Y - arc.EndPoint.Y) < 1e-9)
|
&& System.Math.Abs(start.X - arc.EndPoint.X) < 1e-9
|
||||||
|
&& System.Math.Abs(start.Y - arc.EndPoint.Y) < 1e-9
|
||||||
|
)
|
||||||
{
|
{
|
||||||
sweep = 2 * System.Math.PI;
|
sweep = 2 * System.Math.PI;
|
||||||
}
|
}
|
||||||
@@ -215,7 +241,8 @@ namespace OpenNest.Posts.GravographIS
|
|||||||
maxAngleStep = System.Math.PI / 32;
|
maxAngleStep = System.Math.PI / 32;
|
||||||
|
|
||||||
var steps = (int)System.Math.Ceiling(sweep / maxAngleStep);
|
var steps = (int)System.Math.Ceiling(sweep / maxAngleStep);
|
||||||
if (steps < 1) steps = 1;
|
if (steps < 1)
|
||||||
|
steps = 1;
|
||||||
|
|
||||||
var direction = arc.Rotation == RotationType.CW ? -1.0 : 1.0;
|
var direction = arc.Rotation == RotationType.CW ? -1.0 : 1.0;
|
||||||
for (int i = 1; i < steps; i++)
|
for (int i = 1; i < steps; i++)
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ public class ConvertGeometryLayerTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void AddArc_EngraveLayer_TagsScribe()
|
public void AddArc_EngraveLayer_TagsScribe()
|
||||||
{
|
{
|
||||||
var arc = new Arc(new Vector(0, 0), 1, 0, System.Math.PI / 2) { Layer = new Layer("ENGRAVE") };
|
var arc = new Arc(new Vector(0, 0), 1, 0, System.Math.PI / 2)
|
||||||
|
{
|
||||||
|
Layer = new Layer("ENGRAVE"),
|
||||||
|
};
|
||||||
|
|
||||||
var pgm = ProgramFor(arc);
|
var pgm = ProgramFor(arc);
|
||||||
|
|
||||||
|
|||||||
@@ -166,8 +166,14 @@ public class GeometrySimplifierTests
|
|||||||
// Arc must be tangent to the adjacent straight edges at its endpoints
|
// Arc must be tangent to the adjacent straight edges at its endpoints
|
||||||
var startDelta = AngleBetweenDeg(ArcTangentAt(arc, arc.StartPoint()), new Vector(1, 0));
|
var startDelta = AngleBetweenDeg(ArcTangentAt(arc, arc.StartPoint()), new Vector(1, 0));
|
||||||
var endDelta = AngleBetweenDeg(ArcTangentAt(arc, arc.EndPoint()), new Vector(0, 1));
|
var endDelta = AngleBetweenDeg(ArcTangentAt(arc, arc.EndPoint()), new Vector(0, 1));
|
||||||
Assert.True(startDelta < 0.3, $"Arc start not tangent to incoming line: off by {startDelta:F3} deg");
|
Assert.True(
|
||||||
Assert.True(endDelta < 0.3, $"Arc end not tangent to outgoing line: off by {endDelta:F3} deg");
|
startDelta < 0.3,
|
||||||
|
$"Arc start not tangent to incoming line: off by {startDelta:F3} deg"
|
||||||
|
);
|
||||||
|
Assert.True(
|
||||||
|
endDelta < 0.3,
|
||||||
|
$"Arc end not tangent to outgoing line: off by {endDelta:F3} deg"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -184,7 +190,12 @@ public class GeometrySimplifierTests
|
|||||||
{
|
{
|
||||||
var ang = OpenNest.Math.Angle.ToRadians(10 * i);
|
var ang = OpenNest.Math.Angle.ToRadians(10 * i);
|
||||||
var radius = r1 + deltas1[i];
|
var radius = r1 + deltas1[i];
|
||||||
pts.Add(new Vector(c1.X + radius * System.Math.Cos(ang), c1.Y + radius * System.Math.Sin(ang)));
|
pts.Add(
|
||||||
|
new Vector(
|
||||||
|
c1.X + radius * System.Math.Cos(ang),
|
||||||
|
c1.Y + radius * System.Math.Sin(ang)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second arc center along the junction radius so tangents match at the junction
|
// Second arc center along the junction radius so tangents match at the junction
|
||||||
@@ -197,7 +208,12 @@ public class GeometrySimplifierTests
|
|||||||
{
|
{
|
||||||
var ang = OpenNest.Math.Angle.ToRadians(60 + 8 * i);
|
var ang = OpenNest.Math.Angle.ToRadians(60 + 8 * i);
|
||||||
var radius = r2 + deltas2[i];
|
var radius = r2 + deltas2[i];
|
||||||
pts.Add(new Vector(c2.X + radius * System.Math.Cos(ang), c2.Y + radius * System.Math.Sin(ang)));
|
pts.Add(
|
||||||
|
new Vector(
|
||||||
|
c2.X + radius * System.Math.Cos(ang),
|
||||||
|
c2.Y + radius * System.Math.Sin(ang)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var shape = new Shape();
|
var shape = new Shape();
|
||||||
@@ -215,8 +231,14 @@ public class GeometrySimplifierTests
|
|||||||
Assert.True(arcA.EndPoint().DistanceTo(arcB.StartPoint()) < 1e-6);
|
Assert.True(arcA.EndPoint().DistanceTo(arcB.StartPoint()) < 1e-6);
|
||||||
|
|
||||||
// Tangent continuity across the junction
|
// Tangent continuity across the junction
|
||||||
var junctionDelta = AngleBetweenDeg(ArcTangentAt(arcA, arcA.EndPoint()), ArcTangentAt(arcB, arcB.StartPoint()));
|
var junctionDelta = AngleBetweenDeg(
|
||||||
Assert.True(junctionDelta < 0.3, $"Tangent break of {junctionDelta:F3} deg at arc-arc junction");
|
ArcTangentAt(arcA, arcA.EndPoint()),
|
||||||
|
ArcTangentAt(arcB, arcB.StartPoint())
|
||||||
|
);
|
||||||
|
Assert.True(
|
||||||
|
junctionDelta < 0.3,
|
||||||
|
$"Tangent break of {junctionDelta:F3} deg at arc-arc junction"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector ArcTangentAt(Arc arc, Vector pt)
|
private static Vector ArcTangentAt(Arc arc, Vector pt)
|
||||||
|
|||||||
@@ -7,19 +7,21 @@ namespace OpenNest.Tests.GravographIS;
|
|||||||
|
|
||||||
public class GravographISPostProcessorTests
|
public class GravographISPostProcessorTests
|
||||||
{
|
{
|
||||||
private static LayeredPolyline Poly(LayerType layer, params Vector[] pts)
|
private static LayeredPolyline Poly(LayerType layer, params Vector[] pts) =>
|
||||||
=> new LayeredPolyline(new List<Vector>(pts), layer);
|
new LayeredPolyline(new List<Vector>(pts), layer);
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BuildPasses_EngraveAndCut_OrdersEngraveFirstThenCutWithPause()
|
public void BuildPasses_EngraveAndCut_OrdersEngraveFirstThenCutWithPause()
|
||||||
{
|
{
|
||||||
var post = new GravographISPostProcessor();
|
var post = new GravographISPostProcessor();
|
||||||
|
|
||||||
var passes = post.BuildPasses(new[]
|
var passes = post.BuildPasses(
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
Poly(LayerType.Cut, new Vector(0, 0), new Vector(1, 0)),
|
Poly(LayerType.Cut, new Vector(0, 0), new Vector(1, 0)),
|
||||||
Poly(LayerType.Scribe, new Vector(0, 0), new Vector(0, 1)),
|
Poly(LayerType.Scribe, new Vector(0, 0), new Vector(0, 1)),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Assert.Equal(2, passes.Count);
|
Assert.Equal(2, passes.Count);
|
||||||
|
|
||||||
@@ -36,10 +38,9 @@ public class GravographISPostProcessorTests
|
|||||||
{
|
{
|
||||||
var post = new GravographISPostProcessor();
|
var post = new GravographISPostProcessor();
|
||||||
|
|
||||||
var passes = post.BuildPasses(new[]
|
var passes = post.BuildPasses(
|
||||||
{
|
new[] { Poly(LayerType.Cut, new Vector(0, 0), new Vector(1, 0)) }
|
||||||
Poly(LayerType.Cut, new Vector(0, 0), new Vector(1, 0)),
|
);
|
||||||
});
|
|
||||||
|
|
||||||
Assert.Single(passes);
|
Assert.Single(passes);
|
||||||
Assert.Equal(post.Config.Cut.FeedMmPerSec, passes[0].FeedMmPerSec);
|
Assert.Equal(post.Config.Cut.FeedMmPerSec, passes[0].FeedMmPerSec);
|
||||||
@@ -50,11 +51,13 @@ public class GravographISPostProcessorTests
|
|||||||
{
|
{
|
||||||
var post = new GravographISPostProcessor();
|
var post = new GravographISPostProcessor();
|
||||||
|
|
||||||
var passes = post.BuildPasses(new[]
|
var passes = post.BuildPasses(
|
||||||
|
new[]
|
||||||
{
|
{
|
||||||
Poly(LayerType.Display, new Vector(0, 0), new Vector(1, 0)),
|
Poly(LayerType.Display, new Vector(0, 0), new Vector(1, 0)),
|
||||||
Poly(LayerType.Cut, new Vector(0, 0), new Vector(0, 1)),
|
Poly(LayerType.Cut, new Vector(0, 0), new Vector(0, 1)),
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
Assert.Single(passes);
|
Assert.Single(passes);
|
||||||
Assert.Equal(post.Config.Cut.FeedMmPerSec, passes[0].FeedMmPerSec);
|
Assert.Equal(post.Config.Cut.FeedMmPerSec, passes[0].FeedMmPerSec);
|
||||||
|
|||||||
@@ -191,20 +191,37 @@ public class GravographISWriterTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Passes_PauseBeforeCut_EmitsPauseSequenceBetweenGroups()
|
public void Passes_PauseBeforeCut_EmitsPauseSequenceBetweenGroups()
|
||||||
{
|
{
|
||||||
var engrave = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(1, 0) } };
|
var engrave = new List<IReadOnlyList<Vector>>
|
||||||
|
{
|
||||||
|
new[] { new Vector(0, 0), new Vector(1, 0) },
|
||||||
|
};
|
||||||
var cut = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(0, -1) } };
|
var cut = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(0, -1) } };
|
||||||
var passes = new List<GravographPass>
|
var passes = new List<GravographPass>
|
||||||
{
|
{
|
||||||
new GravographPass { Polylines = engrave, FeedMmPerSec = 10, DepthInches = 0.25 },
|
new GravographPass
|
||||||
new GravographPass { Polylines = cut, FeedMmPerSec = 3, DepthInches = 0.25, PauseBefore = true, PauseMessage = "Hi" },
|
{
|
||||||
|
Polylines = engrave,
|
||||||
|
FeedMmPerSec = 10,
|
||||||
|
DepthInches = 0.25,
|
||||||
|
},
|
||||||
|
new GravographPass
|
||||||
|
{
|
||||||
|
Polylines = cut,
|
||||||
|
FeedMmPerSec = 3,
|
||||||
|
DepthInches = 0.25,
|
||||||
|
PauseBefore = true,
|
||||||
|
PauseMessage = "Hi",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
new GravographISWriter(new GravographISWriterOptions
|
new GravographISWriter(
|
||||||
|
new GravographISWriterOptions
|
||||||
{
|
{
|
||||||
EnvelopeGuardEnabled = false,
|
EnvelopeGuardEnabled = false,
|
||||||
ReturnToOriginAtEnd = false,
|
ReturnToOriginAtEnd = false,
|
||||||
}).Write(passes, ms);
|
}
|
||||||
|
).Write(passes, ms);
|
||||||
var bytes = ms.ToArray();
|
var bytes = ms.ToArray();
|
||||||
|
|
||||||
var mcOff = IndexOf(bytes, 0, (byte)'M', (byte)'C', 0x00, 0x00);
|
var mcOff = IndexOf(bytes, 0, (byte)'M', (byte)'C', 0x00, 0x00);
|
||||||
@@ -217,9 +234,16 @@ public class GravographISWriterTests
|
|||||||
var mcOn = IndexOf(bytes, lbEnd, (byte)'M', (byte)'C', 0x00, 0x01);
|
var mcOn = IndexOf(bytes, lbEnd, (byte)'M', (byte)'C', 0x00, 0x01);
|
||||||
|
|
||||||
Assert.True(mcOff >= 0, "motor-off (MC 0000) not found");
|
Assert.True(mcOff >= 0, "motor-off (MC 0000) not found");
|
||||||
Assert.True(mcOff < ouFb && ouFb < ouFa && ouFa < lbBegin && lbBegin < lbMsg
|
Assert.True(
|
||||||
&& lbMsg < nr && nr < lbEnd && lbEnd < mcOn,
|
mcOff < ouFb
|
||||||
"pause commands out of order");
|
&& ouFb < ouFa
|
||||||
|
&& ouFa < lbBegin
|
||||||
|
&& lbBegin < lbMsg
|
||||||
|
&& lbMsg < nr
|
||||||
|
&& nr < lbEnd
|
||||||
|
&& lbEnd < mcOn,
|
||||||
|
"pause commands out of order"
|
||||||
|
);
|
||||||
|
|
||||||
// Resume sets the cut feed inline (VS 0x0003) after the motor restarts.
|
// Resume sets the cut feed inline (VS 0x0003) after the motor restarts.
|
||||||
var vsCut = IndexOf(bytes, mcOn, (byte)'V', (byte)'S', 0x00, 0x03);
|
var vsCut = IndexOf(bytes, mcOn, (byte)'V', (byte)'S', 0x00, 0x03);
|
||||||
@@ -231,12 +255,34 @@ public class GravographISWriterTests
|
|||||||
{
|
{
|
||||||
var passes = new List<GravographPass>
|
var passes = new List<GravographPass>
|
||||||
{
|
{
|
||||||
new GravographPass { Polylines = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(1, 0) } }, FeedMmPerSec = 10 },
|
new GravographPass
|
||||||
new GravographPass { Polylines = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(0, -1) } }, FeedMmPerSec = 3, PauseBefore = true, PauseMessage = "abc" },
|
{
|
||||||
|
Polylines = new List<IReadOnlyList<Vector>>
|
||||||
|
{
|
||||||
|
new[] { new Vector(0, 0), new Vector(1, 0) },
|
||||||
|
},
|
||||||
|
FeedMmPerSec = 10,
|
||||||
|
},
|
||||||
|
new GravographPass
|
||||||
|
{
|
||||||
|
Polylines = new List<IReadOnlyList<Vector>>
|
||||||
|
{
|
||||||
|
new[] { new Vector(0, 0), new Vector(0, -1) },
|
||||||
|
},
|
||||||
|
FeedMmPerSec = 3,
|
||||||
|
PauseBefore = true,
|
||||||
|
PauseMessage = "abc",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
new GravographISWriter(new GravographISWriterOptions { EnvelopeGuardEnabled = false, ReturnToOriginAtEnd = false }).Write(passes, ms);
|
new GravographISWriter(
|
||||||
|
new GravographISWriterOptions
|
||||||
|
{
|
||||||
|
EnvelopeGuardEnabled = false,
|
||||||
|
ReturnToOriginAtEnd = false,
|
||||||
|
}
|
||||||
|
).Write(passes, ms);
|
||||||
var bytes = ms.ToArray();
|
var bytes = ms.ToArray();
|
||||||
|
|
||||||
var lbAb = IndexOf(bytes, 0, (byte)'L', (byte)'B', (byte)'a', (byte)'b');
|
var lbAb = IndexOf(bytes, 0, (byte)'L', (byte)'B', (byte)'a', (byte)'b');
|
||||||
@@ -250,24 +296,57 @@ public class GravographISWriterTests
|
|||||||
{
|
{
|
||||||
var passes = new List<GravographPass>
|
var passes = new List<GravographPass>
|
||||||
{
|
{
|
||||||
new GravographPass { Polylines = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(1, 0) } }, FeedMmPerSec = 10 },
|
new GravographPass
|
||||||
new GravographPass { Polylines = new List<IReadOnlyList<Vector>> { new[] { new Vector(0, 0), new Vector(0, -1) } }, FeedMmPerSec = 3, PauseBefore = false },
|
{
|
||||||
|
Polylines = new List<IReadOnlyList<Vector>>
|
||||||
|
{
|
||||||
|
new[] { new Vector(0, 0), new Vector(1, 0) },
|
||||||
|
},
|
||||||
|
FeedMmPerSec = 10,
|
||||||
|
},
|
||||||
|
new GravographPass
|
||||||
|
{
|
||||||
|
Polylines = new List<IReadOnlyList<Vector>>
|
||||||
|
{
|
||||||
|
new[] { new Vector(0, 0), new Vector(0, -1) },
|
||||||
|
},
|
||||||
|
FeedMmPerSec = 3,
|
||||||
|
PauseBefore = false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
new GravographISWriter(new GravographISWriterOptions { EnvelopeGuardEnabled = false, ReturnToOriginAtEnd = false }).Write(passes, ms);
|
new GravographISWriter(
|
||||||
|
new GravographISWriterOptions
|
||||||
|
{
|
||||||
|
EnvelopeGuardEnabled = false,
|
||||||
|
ReturnToOriginAtEnd = false,
|
||||||
|
}
|
||||||
|
).Write(passes, ms);
|
||||||
var bytes = ms.ToArray();
|
var bytes = ms.ToArray();
|
||||||
|
|
||||||
Assert.True(IndexOf(bytes, 0, (byte)'V', (byte)'S', 0x00, 0x03) >= 0, "inline cut feed change missing");
|
Assert.True(
|
||||||
Assert.True(IndexOfCmd(bytes, (byte)'L', (byte)'B') < 0, "no LB message expected without a pause");
|
IndexOf(bytes, 0, (byte)'V', (byte)'S', 0x00, 0x03) >= 0,
|
||||||
|
"inline cut feed change missing"
|
||||||
|
);
|
||||||
|
Assert.True(
|
||||||
|
IndexOfCmd(bytes, (byte)'L', (byte)'B') < 0,
|
||||||
|
"no LB message expected without a pause"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int IndexOf(byte[] bytes, int from, byte c0, byte c1, byte hi, byte lo)
|
private static int IndexOf(byte[] bytes, int from, byte c0, byte c1, byte hi, byte lo)
|
||||||
{
|
{
|
||||||
for (var i = System.Math.Max(0, from); i <= bytes.Length - 6; i++)
|
for (var i = System.Math.Max(0, from); i <= bytes.Length - 6; i++)
|
||||||
{
|
{
|
||||||
if (bytes[i] == 0xFF && bytes[i + 1] == 0xFD && bytes[i + 2] == c0 &&
|
if (
|
||||||
bytes[i + 3] == c1 && bytes[i + 4] == hi && bytes[i + 5] == lo)
|
bytes[i] == 0xFF
|
||||||
|
&& bytes[i + 1] == 0xFD
|
||||||
|
&& bytes[i + 2] == c0
|
||||||
|
&& bytes[i + 3] == c1
|
||||||
|
&& bytes[i + 4] == hi
|
||||||
|
&& bytes[i + 5] == lo
|
||||||
|
)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
@@ -277,7 +356,12 @@ public class GravographISWriterTests
|
|||||||
{
|
{
|
||||||
for (var i = 0; i <= bytes.Length - 4; i++)
|
for (var i = 0; i <= bytes.Length - 4; i++)
|
||||||
{
|
{
|
||||||
if (bytes[i] == 0xFF && bytes[i + 1] == 0xFD && bytes[i + 2] == c0 && bytes[i + 3] == c1)
|
if (
|
||||||
|
bytes[i] == 0xFF
|
||||||
|
&& bytes[i + 1] == 0xFD
|
||||||
|
&& bytes[i + 2] == c0
|
||||||
|
&& bytes[i + 3] == c1
|
||||||
|
)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -55,10 +55,16 @@ public class NestPolylineExtractorTests
|
|||||||
Assert.Equal(2, polylines.Count);
|
Assert.Equal(2, polylines.Count);
|
||||||
|
|
||||||
Assert.Equal(LayerType.Scribe, polylines[0].Layer);
|
Assert.Equal(LayerType.Scribe, polylines[0].Layer);
|
||||||
Assert.Equal(new[] { new Vector(0, 0), new Vector(1, 0), new Vector(2, 0) }, polylines[0].Points);
|
Assert.Equal(
|
||||||
|
new[] { new Vector(0, 0), new Vector(1, 0), new Vector(2, 0) },
|
||||||
|
polylines[0].Points
|
||||||
|
);
|
||||||
|
|
||||||
Assert.Equal(LayerType.Cut, polylines[1].Layer);
|
Assert.Equal(LayerType.Cut, polylines[1].Layer);
|
||||||
Assert.Equal(new[] { new Vector(2, 0), new Vector(2, 1), new Vector(3, 1) }, polylines[1].Points);
|
Assert.Equal(
|
||||||
|
new[] { new Vector(2, 0), new Vector(2, 1), new Vector(3, 1) },
|
||||||
|
polylines[1].Points
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user