572fa06a21
ConvertMode.ToIncremental skipped SubProgramCall codes entirely when computing deltas, so parent motions after a sub-call were encoded as if the tool never moved. Several traversal sites (ConvertProgram, GraphicsHelper, PlateRenderer, CutDirectionArrows, Program.BoundingBox) worked around this with save/restore hacks that treated sub-calls as transparent — but DrawRapids legitimately tracks actual tool position, so after the last hole the first perimeter rapid was applied to the wrong base, drifting the rendered perimeter past the plate edge by roughly the distance to the last hole. Fix the root cause: ToIncremental and ToAbsolute now walk sub-programs to compute where they leave the tool, and advance pos accordingly. The other traversals capture a frameOrigin at entry and compute sub-call placement as frameOrigin + Offset, letting pos advance naturally through the sub recursion. All the save/restore workarounds are removed. Program.BoundingBox also picks up the same frame-origin treatment, which corrects a latent bug where absolute-mode endpoints and nested sub-calls dropped the parent's frame origin. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
426 lines
14 KiB
C#
426 lines
14 KiB
C#
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
|
|
namespace OpenNest
|
|
{
|
|
internal static class GraphicsHelper
|
|
{
|
|
public static GraphicsPath GetGraphicsPath(this Program pgm)
|
|
{
|
|
var path = new GraphicsPath();
|
|
var curpos = Vector.Zero;
|
|
|
|
AddProgram(path, pgm, pgm.Mode, ref curpos);
|
|
|
|
return path;
|
|
}
|
|
|
|
public static GraphicsPath GetGraphicsPath(this Program pgm, Vector origin)
|
|
{
|
|
var path = new GraphicsPath();
|
|
var curpos = origin;
|
|
|
|
AddProgram(path, pgm, pgm.Mode, ref curpos);
|
|
|
|
return path;
|
|
}
|
|
|
|
public static GraphicsPath GetGraphicsPath(this Shape shape)
|
|
{
|
|
var path = new GraphicsPath();
|
|
|
|
AddShape(path, shape);
|
|
|
|
return path;
|
|
}
|
|
|
|
public static Image GetImage(this Program pgm, System.Drawing.Size size)
|
|
{
|
|
return pgm.GetImage(size, Pens.Black, null);
|
|
}
|
|
|
|
public static Image GetImage(this Program pgm, System.Drawing.Size size, Pen pen)
|
|
{
|
|
return pgm.GetImage(size, pen, null);
|
|
}
|
|
|
|
public static Image GetImage(this Program pgm, System.Drawing.Size size, Pen pen, Brush brush)
|
|
{
|
|
var img = new Bitmap(size.Width, size.Height);
|
|
var path = pgm.GetGraphicsPath();
|
|
var bounds = path.GetBounds();
|
|
|
|
var scalex = (size.Height - 10) / bounds.Height;
|
|
var scaley = (size.Width - 10) / bounds.Width;
|
|
var scale = scalex < scaley ? scalex : scaley;
|
|
|
|
var matrix = new Matrix();
|
|
matrix.Scale(scale, -scale);
|
|
|
|
path.Transform(matrix);
|
|
|
|
bounds = path.GetBounds();
|
|
|
|
var offset = new PointF(
|
|
(size.Width - bounds.Width) * 0.5f - bounds.X,
|
|
(size.Height - bounds.Height) * 0.5f - bounds.Y);
|
|
|
|
var graphics = Graphics.FromImage(img);
|
|
graphics.TranslateTransform(offset.X, offset.Y);
|
|
|
|
if (brush != null)
|
|
graphics.FillPath(brush, path);
|
|
|
|
if (pen == null)
|
|
pen = Pens.Black;
|
|
|
|
graphics.DrawPath(pen, path);
|
|
|
|
matrix.Dispose();
|
|
graphics.Dispose();
|
|
|
|
return img;
|
|
}
|
|
|
|
public static void GetGraphicsPaths(this Program pgm, Vector origin,
|
|
out GraphicsPath cutPath, out GraphicsPath leadPath)
|
|
{
|
|
cutPath = new GraphicsPath();
|
|
leadPath = new GraphicsPath();
|
|
var curpos = origin;
|
|
|
|
AddProgramSplit(cutPath, leadPath, pgm, pgm.Mode, ref curpos);
|
|
}
|
|
|
|
private static void AddProgramSplit(GraphicsPath cutPath, GraphicsPath leadPath,
|
|
Program pgm, Mode mode, ref Vector curpos)
|
|
{
|
|
// Capture the frame origin at entry. Sub-program Offsets are relative
|
|
// to this fixed origin, not to the current tool position.
|
|
var frameOrigin = curpos;
|
|
mode = pgm.Mode;
|
|
|
|
for (var i = 0; i < pgm.Length; ++i)
|
|
{
|
|
var code = pgm[i];
|
|
|
|
switch (code.Type)
|
|
{
|
|
case CodeType.ArcMove:
|
|
var arc = (ArcMove)code;
|
|
if (arc.Suppressed)
|
|
{
|
|
var endpt = arc.EndPoint;
|
|
if (mode == Mode.Incremental) endpt += curpos;
|
|
curpos = endpt;
|
|
break;
|
|
}
|
|
var arcPath = (arc.Layer == LayerType.Leadin || arc.Layer == LayerType.Leadout)
|
|
? leadPath : cutPath;
|
|
AddArc(arcPath, arc, mode, ref curpos);
|
|
break;
|
|
|
|
case CodeType.LinearMove:
|
|
var line = (LinearMove)code;
|
|
if (line.Suppressed)
|
|
{
|
|
var endpt = line.EndPoint;
|
|
if (mode == Mode.Incremental) endpt += curpos;
|
|
curpos = endpt;
|
|
break;
|
|
}
|
|
var linePath = (line.Layer == LayerType.Leadin || line.Layer == LayerType.Leadout)
|
|
? leadPath : cutPath;
|
|
AddLine(linePath, line, mode, ref curpos);
|
|
break;
|
|
|
|
case CodeType.RapidMove:
|
|
cutPath.StartFigure();
|
|
leadPath.StartFigure();
|
|
AddLine(cutPath, (RapidMove)code, mode, ref curpos);
|
|
break;
|
|
|
|
case CodeType.SubProgramCall:
|
|
var tmpmode = mode;
|
|
var subpgm = (SubProgramCall)code;
|
|
if (subpgm.Program != null)
|
|
{
|
|
cutPath.StartFigure();
|
|
leadPath.StartFigure();
|
|
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
|
AddProgramSplit(cutPath, leadPath, subpgm.Program, mode, ref curpos);
|
|
}
|
|
mode = tmpmode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddArc(GraphicsPath path, ArcMove arc, Mode mode, ref Vector curpos)
|
|
{
|
|
var endpt = arc.EndPoint;
|
|
var center = arc.CenterPoint;
|
|
|
|
if (mode == Mode.Incremental)
|
|
{
|
|
endpt += curpos;
|
|
center += curpos;
|
|
}
|
|
|
|
// start angle in degrees
|
|
var startAngle = Angle.ToDegrees(System.Math.Atan2(
|
|
curpos.Y - center.Y,
|
|
curpos.X - center.X));
|
|
|
|
// end angle in degrees
|
|
var endAngle = Angle.ToDegrees(System.Math.Atan2(
|
|
endpt.Y - center.Y,
|
|
endpt.X - center.X));
|
|
|
|
endAngle = Angle.NormalizeDeg(endAngle);
|
|
startAngle = Angle.NormalizeDeg(startAngle);
|
|
|
|
if (arc.Rotation == RotationType.CCW && endAngle < startAngle)
|
|
endAngle += 360.0;
|
|
else if (arc.Rotation == RotationType.CW && startAngle < endAngle)
|
|
startAngle += 360.0;
|
|
|
|
var dx = endpt.X - center.X;
|
|
var dy = endpt.Y - center.Y;
|
|
|
|
var radius = System.Math.Sqrt(dx * dx + dy * dy);
|
|
|
|
var pt = new PointF((float)(center.X - radius), (float)(center.Y - radius));
|
|
var size = (float)(radius * 2.0);
|
|
|
|
if (startAngle.IsEqualTo(endAngle))
|
|
{
|
|
path.AddEllipse(pt.X, pt.Y, size, size);
|
|
}
|
|
else
|
|
{
|
|
var sweepAngle = (endAngle - startAngle);
|
|
|
|
path.AddArc(
|
|
pt.X, pt.Y,
|
|
size, size,
|
|
(float)startAngle,
|
|
(float)sweepAngle);
|
|
}
|
|
|
|
curpos = endpt;
|
|
}
|
|
|
|
private static void AddLine(GraphicsPath path, LinearMove line, Mode mode, ref Vector curpos)
|
|
{
|
|
var pt = line.EndPoint;
|
|
|
|
if (mode == Mode.Incremental)
|
|
pt += curpos;
|
|
|
|
var pt1 = new PointF((float)curpos.X, (float)curpos.Y);
|
|
var pt2 = new PointF((float)pt.X, (float)pt.Y);
|
|
|
|
path.AddLine(pt1, pt2);
|
|
|
|
curpos = pt;
|
|
}
|
|
|
|
private static void AddLine(GraphicsPath path, RapidMove line, Mode mode, ref Vector curpos)
|
|
{
|
|
var pt = line.EndPoint;
|
|
|
|
if (mode == Mode.Incremental)
|
|
pt += curpos;
|
|
|
|
curpos = pt;
|
|
}
|
|
|
|
private static void AddProgram(GraphicsPath path, Program pgm, Mode mode, ref Vector curpos)
|
|
{
|
|
// Capture the frame origin at entry. Sub-program Offsets are relative
|
|
// to this fixed origin, not to the current tool position.
|
|
var frameOrigin = curpos;
|
|
mode = pgm.Mode;
|
|
GraphicsPath currentFigure = null;
|
|
|
|
void Flush()
|
|
{
|
|
if (currentFigure != null)
|
|
{
|
|
path.AddPath(currentFigure, false);
|
|
currentFigure.Dispose();
|
|
currentFigure = null;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < pgm.Length; ++i)
|
|
{
|
|
var code = pgm[i];
|
|
|
|
switch (code.Type)
|
|
{
|
|
case CodeType.ArcMove:
|
|
{
|
|
var arc = (ArcMove)code;
|
|
if (arc.Layer != LayerType.Leadin && arc.Layer != LayerType.Leadout)
|
|
{
|
|
if (currentFigure == null) currentFigure = new GraphicsPath();
|
|
AddArc(currentFigure, arc, mode, ref curpos);
|
|
}
|
|
else
|
|
{
|
|
Flush();
|
|
var endpt = arc.EndPoint;
|
|
if (mode == Mode.Incremental) endpt += curpos;
|
|
curpos = endpt;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CodeType.LinearMove:
|
|
{
|
|
var line = (LinearMove)code;
|
|
if (line.Layer != LayerType.Leadin && line.Layer != LayerType.Leadout)
|
|
{
|
|
if (currentFigure == null) currentFigure = new GraphicsPath();
|
|
AddLine(currentFigure, line, mode, ref curpos);
|
|
}
|
|
else
|
|
{
|
|
Flush();
|
|
var endpt = line.EndPoint;
|
|
if (mode == Mode.Incremental) endpt += curpos;
|
|
curpos = endpt;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CodeType.RapidMove:
|
|
Flush();
|
|
AddLine(path, (RapidMove)code, mode, ref curpos);
|
|
break;
|
|
|
|
case CodeType.SubProgramCall:
|
|
{
|
|
Flush();
|
|
var tmpmode = mode;
|
|
var subpgm = (SubProgramCall)code;
|
|
|
|
if (subpgm.Program != null)
|
|
{
|
|
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
|
AddProgram(path, subpgm.Program, mode, ref curpos);
|
|
}
|
|
|
|
mode = tmpmode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Flush();
|
|
}
|
|
|
|
private static void AddArc(GraphicsPath path, Arc arc)
|
|
{
|
|
var diameter = arc.Diameter;
|
|
|
|
var endAngle = Angle.NormalizeDeg(Angle.ToDegrees(arc.EndAngle));
|
|
var startAngle = Angle.NormalizeDeg(Angle.ToDegrees(arc.StartAngle));
|
|
|
|
if (arc.Rotation == RotationType.CCW && endAngle < startAngle)
|
|
endAngle += 360.0;
|
|
else if (arc.Rotation == RotationType.CW && startAngle < endAngle)
|
|
startAngle += 360.0;
|
|
|
|
var sweepAngle = (endAngle - startAngle);
|
|
|
|
path.AddArc(
|
|
(float)(arc.Center.X - arc.Radius),
|
|
(float)(arc.Center.Y - arc.Radius),
|
|
(float)diameter,
|
|
(float)diameter,
|
|
(float)(startAngle),
|
|
(float)sweepAngle);
|
|
}
|
|
|
|
private static void AddCircle(GraphicsPath path, Circle circle)
|
|
{
|
|
var diameter = circle.Diameter;
|
|
|
|
path.AddEllipse(
|
|
(float)(circle.Center.X - circle.Radius),
|
|
(float)(circle.Center.Y - circle.Radius),
|
|
(float)diameter,
|
|
(float)diameter);
|
|
}
|
|
|
|
private static void AddLine(GraphicsPath path, Line line)
|
|
{
|
|
path.AddLine(
|
|
(float)line.StartPoint.X,
|
|
(float)line.StartPoint.Y,
|
|
(float)line.EndPoint.X,
|
|
(float)line.EndPoint.Y);
|
|
}
|
|
|
|
private static void AddShape(GraphicsPath path, Shape shape)
|
|
{
|
|
foreach (var entity in shape.Entities)
|
|
{
|
|
if (entity.Layer != null)
|
|
{
|
|
if (string.Equals(entity.Layer.Name, SpecialLayers.Leadin.Name, System.StringComparison.OrdinalIgnoreCase) ||
|
|
string.Equals(entity.Layer.Name, SpecialLayers.Leadout.Name, System.StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
switch (entity.Type)
|
|
{
|
|
case EntityType.Arc:
|
|
AddArc(path, (Arc)entity);
|
|
break;
|
|
|
|
case EntityType.Circle:
|
|
AddCircle(path, (Circle)entity);
|
|
break;
|
|
|
|
case EntityType.Line:
|
|
AddLine(path, (Line)entity);
|
|
break;
|
|
|
|
case EntityType.Polygon:
|
|
AddPolygon(path, (Polygon)entity);
|
|
break;
|
|
|
|
case EntityType.Shape:
|
|
var subpath = new GraphicsPath();
|
|
AddShape(subpath, (Shape)entity);
|
|
path.AddPath(subpath, false);
|
|
subpath.Dispose();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddPolygon(GraphicsPath path, Polygon polygon)
|
|
{
|
|
var pts = new PointF[polygon.Vertices.Count];
|
|
|
|
for (int i = 0; i < pts.Length; i++)
|
|
{
|
|
var pt = polygon.Vertices[i];
|
|
pts[i] = new PointF((float)pt.X, (float)pt.Y);
|
|
}
|
|
|
|
path.AddPolygon(pts);
|
|
}
|
|
}
|
|
}
|