feat: add Draw Cut Direction view option and extract PlateRenderer
Add a "Draw Cut Direction" toggle to the View menu that draws small arrowheads along cutting paths to indicate the direction of travel. Arrows are placed on both linear and arc moves, spaced ~60px apart, and correctly follow CW/CCW arc tangents. Extract all rendering methods (~660 lines) from PlateView into a new PlateRenderer class, reducing PlateView from 1640 to 979 lines. PlateView retains input handling, selection, zoom, and part management. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,8 +61,8 @@ namespace OpenNest.Controls
|
||||
e.Graphics.SmoothingMode = SmoothingMode.HighSpeed;
|
||||
|
||||
e.Graphics.TranslateTransform(origin.X, origin.Y);
|
||||
DrawPlate(e.Graphics);
|
||||
DrawParts(e.Graphics);
|
||||
Renderer.DrawPlate(e.Graphics);
|
||||
Renderer.DrawParts(e.Graphics);
|
||||
e.Graphics.ResetTransform();
|
||||
|
||||
PaintMetadata(e.Graphics);
|
||||
|
||||
691
OpenNest/Controls/PlateRenderer.cs
Normal file
691
OpenNest/Controls/PlateRenderer.cs
Normal file
@@ -0,0 +1,691 @@
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Controls
|
||||
{
|
||||
internal class PlateRenderer
|
||||
{
|
||||
private readonly PlateView view;
|
||||
|
||||
public PlateRenderer(PlateView view)
|
||||
{
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public void DrawPlate(Graphics g)
|
||||
{
|
||||
var plate = view.Plate;
|
||||
var plateRect = new RectangleF
|
||||
{
|
||||
Width = view.LengthWorldToGui(plate.Size.Length),
|
||||
Height = view.LengthWorldToGui(plate.Size.Width)
|
||||
};
|
||||
|
||||
var edgeSpacingRect = new RectangleF
|
||||
{
|
||||
Width = view.LengthWorldToGui(plate.Size.Length - plate.EdgeSpacing.Left - plate.EdgeSpacing.Right),
|
||||
Height = view.LengthWorldToGui(plate.Size.Width - plate.EdgeSpacing.Top - plate.EdgeSpacing.Bottom)
|
||||
};
|
||||
|
||||
switch (plate.Quadrant)
|
||||
{
|
||||
case 1:
|
||||
plateRect.Location = view.PointWorldToGraph(0, 0);
|
||||
edgeSpacingRect.Location = view.PointWorldToGraph(
|
||||
plate.EdgeSpacing.Left,
|
||||
plate.EdgeSpacing.Bottom);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
plateRect.Location = view.PointWorldToGraph(-plate.Size.Length, 0);
|
||||
edgeSpacingRect.Location = view.PointWorldToGraph(
|
||||
plate.EdgeSpacing.Left - plate.Size.Length,
|
||||
plate.EdgeSpacing.Bottom);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
plateRect.Location = view.PointWorldToGraph(-plate.Size.Length, -plate.Size.Width);
|
||||
edgeSpacingRect.Location = view.PointWorldToGraph(
|
||||
plate.EdgeSpacing.Left - plate.Size.Length,
|
||||
plate.EdgeSpacing.Bottom - plate.Size.Width);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
plateRect.Location = view.PointWorldToGraph(0, -plate.Size.Width);
|
||||
edgeSpacingRect.Location = view.PointWorldToGraph(
|
||||
plate.EdgeSpacing.Left,
|
||||
plate.EdgeSpacing.Bottom - plate.Size.Width);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
plateRect.Y -= plateRect.Height;
|
||||
edgeSpacingRect.Y -= edgeSpacingRect.Height;
|
||||
|
||||
g.FillRectangle(view.ColorScheme.LayoutFillBrush, plateRect);
|
||||
|
||||
var viewBounds = view.GetViewBounds();
|
||||
|
||||
if (!edgeSpacingRect.Contains(viewBounds))
|
||||
{
|
||||
g.DrawRectangle(view.ColorScheme.EdgeSpacingPen,
|
||||
edgeSpacingRect.X,
|
||||
edgeSpacingRect.Y,
|
||||
edgeSpacingRect.Width,
|
||||
edgeSpacingRect.Height);
|
||||
}
|
||||
|
||||
g.DrawRectangle(view.ColorScheme.LayoutOutlinePen,
|
||||
plateRect.X,
|
||||
plateRect.Y,
|
||||
plateRect.Width,
|
||||
plateRect.Height);
|
||||
}
|
||||
|
||||
public void DrawParts(Graphics g)
|
||||
{
|
||||
var viewBounds = view.GetViewBounds();
|
||||
var layoutParts = view.LayoutParts;
|
||||
|
||||
for (var i = 0; i < layoutParts.Count; ++i)
|
||||
{
|
||||
var part = layoutParts[i];
|
||||
|
||||
if (part.IsDirty)
|
||||
part.Update(view);
|
||||
|
||||
var path = part.Path;
|
||||
var pathBounds = path.GetBounds();
|
||||
|
||||
if (!pathBounds.IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
part.Draw(g, (i + 1).ToString());
|
||||
DrawBendLines(g, part.BasePart);
|
||||
DrawEtchMarks(g, part.BasePart);
|
||||
DrawGrainWarning(g, part.BasePart);
|
||||
}
|
||||
|
||||
var previewParts = view.PreviewParts;
|
||||
var previewBrush = view.PreviewBrush;
|
||||
var previewPen = view.PreviewPen;
|
||||
|
||||
for (var i = 0; i < previewParts.Count; i++)
|
||||
{
|
||||
var part = previewParts[i];
|
||||
|
||||
if (part.IsDirty)
|
||||
part.Update(view);
|
||||
|
||||
var path = part.Path;
|
||||
if (!path.GetBounds().IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
g.FillPath(previewBrush, path);
|
||||
g.DrawPath(previewPen, path);
|
||||
}
|
||||
|
||||
if (view.DrawOffset && view.Plate.PartSpacing > 0)
|
||||
DrawOffsetGeometry(g);
|
||||
|
||||
if (view.DrawBounds)
|
||||
{
|
||||
var bounds = view.SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox();
|
||||
DrawBox(g, bounds);
|
||||
}
|
||||
|
||||
if (view.DrawRapid)
|
||||
DrawRapids(g);
|
||||
|
||||
if (view.DrawPiercePoints)
|
||||
DrawAllPiercePoints(g);
|
||||
|
||||
if (view.DrawCutDirection)
|
||||
DrawAllCutDirectionArrows(g);
|
||||
}
|
||||
|
||||
public void DrawCutOffs(Graphics g)
|
||||
{
|
||||
var plate = view.Plate;
|
||||
if (plate?.CutOffs == null || plate.CutOffs.Count == 0)
|
||||
return;
|
||||
|
||||
using var pen = new Pen(Color.FromArgb(64, 64, 64), 1.5f);
|
||||
using var selectedPen = new Pen(Color.FromArgb(0, 120, 255), 3.5f);
|
||||
|
||||
foreach (var cutoff in plate.CutOffs)
|
||||
{
|
||||
var program = cutoff.Drawing?.Program;
|
||||
if (program == null || program.Codes.Count == 0)
|
||||
continue;
|
||||
|
||||
var activePen = cutoff == view.SelectedCutOff ? selectedPen : pen;
|
||||
|
||||
for (var i = 0; i < program.Codes.Count - 1; i += 2)
|
||||
{
|
||||
if (program.Codes[i] is RapidMove rapid &&
|
||||
program.Codes[i + 1] is LinearMove linear)
|
||||
{
|
||||
DrawLine(g, rapid.EndPoint, linear.EndPoint, activePen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawActiveWorkArea(Graphics g)
|
||||
{
|
||||
var workArea = view.ActiveWorkArea;
|
||||
if (workArea == null)
|
||||
return;
|
||||
|
||||
var rect = new RectangleF
|
||||
{
|
||||
Location = view.PointWorldToGraph(workArea.Location),
|
||||
Width = view.LengthWorldToGui(workArea.Width),
|
||||
Height = view.LengthWorldToGui(workArea.Length)
|
||||
};
|
||||
rect.Y -= rect.Height;
|
||||
|
||||
using var pen = new Pen(Color.Red, 1.5f)
|
||||
{
|
||||
DashStyle = DashStyle.Dash
|
||||
};
|
||||
g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
|
||||
}
|
||||
|
||||
private static readonly Color[] PriorityFills =
|
||||
{
|
||||
Color.FromArgb(60, Color.LimeGreen),
|
||||
Color.FromArgb(60, Color.Gold),
|
||||
Color.FromArgb(60, Color.Salmon),
|
||||
};
|
||||
|
||||
private static readonly Color[] PriorityBorders =
|
||||
{
|
||||
Color.FromArgb(180, Color.Green),
|
||||
Color.FromArgb(180, Color.DarkGoldenrod),
|
||||
Color.FromArgb(180, Color.DarkRed),
|
||||
};
|
||||
|
||||
public void DrawDebugRemnants(Graphics g)
|
||||
{
|
||||
var remnants = view.DebugRemnants;
|
||||
if (remnants == null || remnants.Count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < remnants.Count; i++)
|
||||
{
|
||||
var box = remnants[i];
|
||||
var loc = view.PointWorldToGraph(box.Location);
|
||||
var w = view.LengthWorldToGui(box.Width);
|
||||
var h = view.LengthWorldToGui(box.Length);
|
||||
var rect = new RectangleF(loc.X, loc.Y - h, w, h);
|
||||
|
||||
var priority = view.DebugRemnantPriorities != null && i < view.DebugRemnantPriorities.Count
|
||||
? System.Math.Min(view.DebugRemnantPriorities[i], 2)
|
||||
: 0;
|
||||
|
||||
using var brush = new SolidBrush(PriorityFills[priority]);
|
||||
g.FillRectangle(brush, rect);
|
||||
|
||||
using var pen = new Pen(PriorityBorders[priority], 1.5f);
|
||||
g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
|
||||
|
||||
var label = $"P{priority} {box.Width:F1}x{box.Length:F1}";
|
||||
using var font = new Font("Segoe UI", 8f);
|
||||
using var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
|
||||
g.DrawString(label, font, Brushes.Black, rect, sf);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBendLines(Graphics g, Part part)
|
||||
{
|
||||
if (!view.ShowBendLines || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
using var bendPen = new Pen(Color.Yellow, 1.5f)
|
||||
{
|
||||
DashStyle = System.Drawing.Drawing2D.DashStyle.Dash
|
||||
};
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
var start = bend.StartPoint;
|
||||
var end = bend.EndPoint;
|
||||
|
||||
if (part.Rotation != 0)
|
||||
{
|
||||
start = start.Rotate(part.Rotation);
|
||||
end = end.Rotate(part.Rotation);
|
||||
}
|
||||
|
||||
start = start + part.Location;
|
||||
end = end + part.Location;
|
||||
|
||||
var pt1 = view.PointWorldToGraph(start);
|
||||
var pt2 = view.PointWorldToGraph(end);
|
||||
|
||||
g.DrawLine(bendPen, pt1, pt2);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEtchMarks(Graphics g, Part part)
|
||||
{
|
||||
if (!view.ShowBendLines || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
using var etchPen = new Pen(Color.Green, 1.5f);
|
||||
var etchLength = 1.0;
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
if (bend.Direction != BendDirection.Up)
|
||||
continue;
|
||||
|
||||
var start = bend.StartPoint;
|
||||
var end = bend.EndPoint;
|
||||
|
||||
if (part.Rotation != 0)
|
||||
{
|
||||
start = start.Rotate(part.Rotation);
|
||||
end = end.Rotate(part.Rotation);
|
||||
}
|
||||
|
||||
start = start + part.Location;
|
||||
end = end + part.Location;
|
||||
|
||||
var length = bend.Length;
|
||||
var angle = bend.StartPoint.AngleTo(bend.EndPoint) + part.Rotation;
|
||||
|
||||
if (length < etchLength * 3.0)
|
||||
{
|
||||
var pt1 = view.PointWorldToGraph(start);
|
||||
var pt2 = view.PointWorldToGraph(end);
|
||||
g.DrawLine(etchPen, pt1, pt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dx = System.Math.Cos(angle) * etchLength;
|
||||
var dy = System.Math.Sin(angle) * etchLength;
|
||||
|
||||
var s1 = view.PointWorldToGraph(start);
|
||||
var e1 = view.PointWorldToGraph(new Vector(start.X + dx, start.Y + dy));
|
||||
g.DrawLine(etchPen, s1, e1);
|
||||
|
||||
var s2 = view.PointWorldToGraph(end);
|
||||
var e2 = view.PointWorldToGraph(new Vector(end.X - dx, end.Y - dy));
|
||||
g.DrawLine(etchPen, s2, e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGrainWarning(Graphics g, Part part)
|
||||
{
|
||||
var plate = view.Plate;
|
||||
if (!view.ShowBendLines || plate == null || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
var grainAngle = plate.GrainAngle;
|
||||
var tolerance = Angle.ToRadians(5);
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
var bendAngle = bend.LineAngle + part.Rotation;
|
||||
bendAngle = bendAngle % System.Math.PI;
|
||||
if (bendAngle < 0) bendAngle += System.Math.PI;
|
||||
|
||||
var grainNormalized = grainAngle % System.Math.PI;
|
||||
if (grainNormalized < 0) grainNormalized += System.Math.PI;
|
||||
|
||||
var diff = System.Math.Abs(bendAngle - grainNormalized);
|
||||
diff = System.Math.Min(diff, System.Math.PI - diff);
|
||||
|
||||
if (diff > tolerance)
|
||||
{
|
||||
var box = part.BaseDrawing.Program.BoundingBox();
|
||||
var location = part.Location;
|
||||
var pt1 = view.PointWorldToGraph(location);
|
||||
var pt2 = view.PointWorldToGraph(new Vector(
|
||||
location.X + box.Width, location.Y + box.Length));
|
||||
using var warnPen = new Pen(Color.FromArgb(180, 255, 140, 0), 2f);
|
||||
g.DrawRectangle(warnPen, pt1.X, pt2.Y,
|
||||
System.Math.Abs(pt2.X - pt1.X), System.Math.Abs(pt2.Y - pt1.Y));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOffsetGeometry(Graphics g)
|
||||
{
|
||||
var layoutParts = view.LayoutParts;
|
||||
using var offsetPen = new Pen(Color.FromArgb(120, 255, 100, 100));
|
||||
|
||||
for (var i = 0; i < layoutParts.Count; i++)
|
||||
{
|
||||
var layoutPart = layoutParts[i];
|
||||
|
||||
if (layoutPart.IsDirty)
|
||||
layoutPart.Update(view);
|
||||
|
||||
layoutPart.UpdateOffset(view.Plate.PartSpacing, view.OffsetTolerance, view.Matrix);
|
||||
|
||||
if (layoutPart.OffsetPath != null)
|
||||
g.DrawPath(offsetPen, layoutPart.OffsetPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRapids(Graphics g)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (var i = 0; i < view.Plate.Parts.Count; ++i)
|
||||
{
|
||||
var part = view.Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
|
||||
var piercePoint = GetFirstPiercePoint(pgm, part.Location);
|
||||
DrawLine(g, pos, piercePoint, view.ColorScheme.RapidPen);
|
||||
|
||||
pos = part.Location;
|
||||
DrawRapids(g, pgm, ref pos, skipFirstRapid: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector GetFirstPiercePoint(Program pgm, Vector partLocation)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; i++)
|
||||
{
|
||||
if (pgm[i] is Motion motion)
|
||||
{
|
||||
if (pgm.Mode == Mode.Incremental)
|
||||
return motion.EndPoint + partLocation;
|
||||
return motion.EndPoint;
|
||||
}
|
||||
}
|
||||
return partLocation;
|
||||
}
|
||||
|
||||
private void DrawRapids(Graphics g, Program pgm, ref Vector pos, bool skipFirstRapid = false)
|
||||
{
|
||||
var firstRapidSkipped = false;
|
||||
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
var code = pgm[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var program = subpgm.Program;
|
||||
|
||||
if (program != null)
|
||||
DrawRapids(g, program, ref pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
if (pgm.Mode == Mode.Incremental)
|
||||
{
|
||||
var endpt = motion.EndPoint + pos;
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
firstRapidSkipped = true;
|
||||
else
|
||||
DrawLine(g, pos, endpt, view.ColorScheme.RapidPen);
|
||||
}
|
||||
pos = endpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
firstRapidSkipped = true;
|
||||
else
|
||||
DrawLine(g, pos, motion.EndPoint, view.ColorScheme.RapidPen);
|
||||
}
|
||||
pos = motion.EndPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAllPiercePoints(Graphics g)
|
||||
{
|
||||
using var brush = new SolidBrush(Color.Red);
|
||||
using var pen = new Pen(Color.DarkRed, 1f);
|
||||
|
||||
for (var i = 0; i < view.Plate.Parts.Count; ++i)
|
||||
{
|
||||
var part = view.Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
var pos = part.Location;
|
||||
DrawProgramPiercePoints(g, pgm, ref pos, brush, pen);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProgramPiercePoints(Graphics g, Program pgm, ref Vector pos, Brush brush, Pen pen)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
var code = pgm[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program != null)
|
||||
DrawProgramPiercePoints(g, subpgm.Program, ref pos, brush, pen);
|
||||
}
|
||||
else
|
||||
{
|
||||
var motion = code as Motion;
|
||||
if (motion == null) continue;
|
||||
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint;
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
var pt = view.PointWorldToGraph(endpt);
|
||||
var radius = 2f;
|
||||
g.FillEllipse(brush, pt.X - radius, pt.Y - radius, radius * 2, radius * 2);
|
||||
g.DrawEllipse(pen, pt.X - radius, pt.Y - radius, radius * 2, radius * 2);
|
||||
}
|
||||
|
||||
pos = endpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAllCutDirectionArrows(Graphics g)
|
||||
{
|
||||
using var pen = new Pen(Color.FromArgb(220, Color.DarkCyan), 1.5f);
|
||||
using var brush = new SolidBrush(Color.FromArgb(220, Color.DarkCyan));
|
||||
|
||||
var arrowSpacingWorld = view.LengthGuiToWorld(60f);
|
||||
var arrowSize = 5f;
|
||||
|
||||
for (var i = 0; i < view.Plate.Parts.Count; ++i)
|
||||
{
|
||||
var part = view.Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
var pos = part.Location;
|
||||
DrawProgramCutDirectionArrows(g, pgm, ref pos, pen, brush, arrowSpacingWorld, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProgramCutDirectionArrows(Graphics g, Program pgm, ref Vector pos,
|
||||
Pen pen, Brush brush, double spacing, float arrowSize)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
var code = pgm[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program != null)
|
||||
DrawProgramCutDirectionArrows(g, subpgm.Program, ref pos, pen, brush, spacing, arrowSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (code is not Motion motion) continue;
|
||||
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint;
|
||||
|
||||
if (code.Type == CodeType.LinearMove)
|
||||
{
|
||||
var line = (LinearMove)code;
|
||||
if (!line.Suppressed)
|
||||
DrawLineDirectionArrows(g, pos, endpt, brush, spacing, arrowSize);
|
||||
}
|
||||
else if (code.Type == CodeType.ArcMove)
|
||||
{
|
||||
var arc = (ArcMove)code;
|
||||
if (!arc.Suppressed)
|
||||
{
|
||||
var center = pgm.Mode == Mode.Incremental
|
||||
? arc.CenterPoint + pos
|
||||
: arc.CenterPoint;
|
||||
DrawArcDirectionArrows(g, pos, endpt, center, arc.Rotation, brush, spacing, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
pos = endpt;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLineDirectionArrows(Graphics g, Vector start, Vector end,
|
||||
Brush brush, double spacing, float arrowSize)
|
||||
{
|
||||
var dx = end.X - start.X;
|
||||
var dy = end.Y - start.Y;
|
||||
var length = System.Math.Sqrt(dx * dx + dy * dy);
|
||||
if (length < spacing * 0.5) return;
|
||||
|
||||
var dirX = dx / length;
|
||||
var dirY = dy / length;
|
||||
|
||||
var count = System.Math.Max(1, (int)(length / spacing));
|
||||
var step = length / (count + 1);
|
||||
|
||||
for (var i = 1; i <= count; i++)
|
||||
{
|
||||
var t = step * i;
|
||||
var pt = new Vector(start.X + dirX * t, start.Y + dirY * t);
|
||||
var screenPt = view.PointWorldToGraph(pt);
|
||||
var angle = System.Math.Atan2(-dirY, dirX);
|
||||
DrawArrowHead(g, brush, screenPt, angle, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawArcDirectionArrows(Graphics g, Vector start, Vector end, Vector center,
|
||||
RotationType rotation, Brush brush, double spacing, float arrowSize)
|
||||
{
|
||||
var radius = center.DistanceTo(start);
|
||||
if (radius < Tolerance.Epsilon) return;
|
||||
|
||||
var startAngle = System.Math.Atan2(start.Y - center.Y, start.X - center.X);
|
||||
var endAngle = System.Math.Atan2(end.Y - center.Y, end.X - center.X);
|
||||
|
||||
double sweep;
|
||||
if (rotation == RotationType.CCW)
|
||||
{
|
||||
sweep = endAngle - startAngle;
|
||||
if (sweep <= 0) sweep += 2 * System.Math.PI;
|
||||
}
|
||||
else
|
||||
{
|
||||
sweep = startAngle - endAngle;
|
||||
if (sweep <= 0) sweep += 2 * System.Math.PI;
|
||||
}
|
||||
|
||||
var arcLength = radius * System.Math.Abs(sweep);
|
||||
if (arcLength < spacing * 0.5) return;
|
||||
|
||||
var count = System.Math.Max(1, (int)(arcLength / spacing));
|
||||
var stepAngle = sweep / (count + 1);
|
||||
|
||||
for (var i = 1; i <= count; i++)
|
||||
{
|
||||
double angle;
|
||||
if (rotation == RotationType.CCW)
|
||||
angle = startAngle + stepAngle * i;
|
||||
else
|
||||
angle = startAngle - stepAngle * i;
|
||||
|
||||
var pt = new Vector(
|
||||
center.X + radius * System.Math.Cos(angle),
|
||||
center.Y + radius * System.Math.Sin(angle));
|
||||
var screenPt = view.PointWorldToGraph(pt);
|
||||
|
||||
double tangent;
|
||||
if (rotation == RotationType.CCW)
|
||||
tangent = angle + System.Math.PI / 2;
|
||||
else
|
||||
tangent = angle - System.Math.PI / 2;
|
||||
|
||||
var screenAngle = System.Math.Atan2(-System.Math.Sin(tangent), System.Math.Cos(tangent));
|
||||
DrawArrowHead(g, brush, screenPt, screenAngle, arrowSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawArrowHead(Graphics g, Brush brush, PointF tip, double angle, float size)
|
||||
{
|
||||
var sin = (float)System.Math.Sin(angle);
|
||||
var cos = (float)System.Math.Cos(angle);
|
||||
|
||||
var backX = -size * cos;
|
||||
var backY = -size * sin;
|
||||
var wingX = size * 0.5f * sin;
|
||||
var wingY = -size * 0.5f * cos;
|
||||
|
||||
var points = new PointF[]
|
||||
{
|
||||
tip,
|
||||
new PointF(tip.X + backX + wingX, tip.Y + backY + wingY),
|
||||
new PointF(tip.X + backX - wingX, tip.Y + backY - wingY),
|
||||
};
|
||||
|
||||
g.FillPolygon(brush, points);
|
||||
}
|
||||
|
||||
private void DrawLine(Graphics g, Vector pt1, Vector pt2, Pen pen)
|
||||
{
|
||||
var point1 = view.PointWorldToGraph(pt1);
|
||||
var point2 = view.PointWorldToGraph(pt2);
|
||||
|
||||
g.DrawLine(pen, point1, point2);
|
||||
}
|
||||
|
||||
private void DrawBox(Graphics g, Box box)
|
||||
{
|
||||
var rect = new RectangleF
|
||||
{
|
||||
Location = view.PointWorldToGraph(box.Location),
|
||||
Width = view.LengthWorldToGui(box.Width),
|
||||
Height = view.LengthWorldToGui(box.Length)
|
||||
};
|
||||
|
||||
g.DrawRectangle(view.ColorScheme.BoundingBoxPen, rect.X, rect.Y - rect.Height, rect.Width, rect.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using OpenNest.Actions;
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Engine.Fill;
|
||||
@@ -41,6 +40,7 @@ namespace OpenNest.Controls
|
||||
private Point middleMouseDownPoint;
|
||||
private Box activeWorkArea;
|
||||
private List<Box> debugRemnants;
|
||||
private PlateRenderer renderer;
|
||||
|
||||
public Box ActiveWorkArea
|
||||
{
|
||||
@@ -114,6 +114,7 @@ namespace OpenNest.Controls
|
||||
DrawBounds = true;
|
||||
DrawOffset = false;
|
||||
FillParts = true;
|
||||
renderer = new PlateRenderer(this);
|
||||
SetAction(typeof(ActionSelect));
|
||||
|
||||
UpdateMatrix();
|
||||
@@ -137,12 +138,30 @@ namespace OpenNest.Controls
|
||||
|
||||
public bool DrawOffset { get; set; }
|
||||
|
||||
public bool DrawCutDirection { get; set; }
|
||||
|
||||
public bool ShowBendLines { get; set; }
|
||||
|
||||
public double OffsetTolerance { get; set; } = 0.001;
|
||||
|
||||
public bool FillParts { get; set; }
|
||||
|
||||
internal List<LayoutPart> LayoutParts => parts;
|
||||
|
||||
internal IReadOnlyList<LayoutPart> PreviewParts =>
|
||||
activeParts.Count > 0 ? activeParts : stationaryParts;
|
||||
|
||||
internal Brush PreviewBrush =>
|
||||
activeParts.Count > 0 ? ColorScheme.ActivePreviewPartBrush : ColorScheme.PreviewPartBrush;
|
||||
|
||||
internal Pen PreviewPen =>
|
||||
activeParts.Count > 0 ? ColorScheme.ActivePreviewPartPen : ColorScheme.PreviewPartPen;
|
||||
|
||||
internal RectangleF GetViewBounds() =>
|
||||
new RectangleF(-origin.X, -origin.Y, Width, Height);
|
||||
|
||||
internal PlateRenderer Renderer => renderer;
|
||||
|
||||
public CutOffSettings CutOffSettings
|
||||
{
|
||||
get => cutOffSettings;
|
||||
@@ -465,11 +484,11 @@ namespace OpenNest.Controls
|
||||
|
||||
e.Graphics.TranslateTransform(origin.X, origin.Y);
|
||||
|
||||
DrawPlate(e.Graphics);
|
||||
DrawParts(e.Graphics);
|
||||
DrawCutOffs(e.Graphics);
|
||||
DrawActiveWorkArea(e.Graphics);
|
||||
DrawDebugRemnants(e.Graphics);
|
||||
renderer.DrawPlate(e.Graphics);
|
||||
renderer.DrawParts(e.Graphics);
|
||||
renderer.DrawCutOffs(e.Graphics);
|
||||
renderer.DrawActiveWorkArea(e.Graphics);
|
||||
renderer.DrawDebugRemnants(e.Graphics);
|
||||
|
||||
base.OnPaint(e);
|
||||
}
|
||||
@@ -494,284 +513,6 @@ namespace OpenNest.Controls
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
protected void DrawPlate(Graphics g)
|
||||
{
|
||||
var plateRect = new RectangleF
|
||||
{
|
||||
Width = LengthWorldToGui(Plate.Size.Length),
|
||||
Height = LengthWorldToGui(Plate.Size.Width)
|
||||
};
|
||||
|
||||
var edgeSpacingRect = new RectangleF
|
||||
{
|
||||
Width = LengthWorldToGui(Plate.Size.Length - Plate.EdgeSpacing.Left - Plate.EdgeSpacing.Right),
|
||||
Height = LengthWorldToGui(Plate.Size.Width - Plate.EdgeSpacing.Top - Plate.EdgeSpacing.Bottom)
|
||||
};
|
||||
|
||||
switch (Plate.Quadrant)
|
||||
{
|
||||
case 1:
|
||||
plateRect.Location = PointWorldToGraph(0, 0);
|
||||
edgeSpacingRect.Location = PointWorldToGraph(
|
||||
Plate.EdgeSpacing.Left,
|
||||
Plate.EdgeSpacing.Bottom);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
plateRect.Location = PointWorldToGraph(-Plate.Size.Length, 0);
|
||||
edgeSpacingRect.Location = PointWorldToGraph(
|
||||
Plate.EdgeSpacing.Left - Plate.Size.Length,
|
||||
Plate.EdgeSpacing.Bottom);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
plateRect.Location = PointWorldToGraph(-Plate.Size.Length, -Plate.Size.Width);
|
||||
edgeSpacingRect.Location = PointWorldToGraph(
|
||||
Plate.EdgeSpacing.Left - Plate.Size.Length,
|
||||
Plate.EdgeSpacing.Bottom - Plate.Size.Width);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
plateRect.Location = PointWorldToGraph(0, -Plate.Size.Width);
|
||||
edgeSpacingRect.Location = PointWorldToGraph(
|
||||
Plate.EdgeSpacing.Left,
|
||||
Plate.EdgeSpacing.Bottom - Plate.Size.Width);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
plateRect.Y -= plateRect.Height;
|
||||
edgeSpacingRect.Y -= edgeSpacingRect.Height;
|
||||
|
||||
g.FillRectangle(ColorScheme.LayoutFillBrush, plateRect);
|
||||
|
||||
var viewBounds = new RectangleF(-origin.X, -origin.Y, Width, Height);
|
||||
|
||||
if (!edgeSpacingRect.Contains(viewBounds))
|
||||
{
|
||||
g.DrawRectangle(ColorScheme.EdgeSpacingPen,
|
||||
edgeSpacingRect.X,
|
||||
edgeSpacingRect.Y,
|
||||
edgeSpacingRect.Width,
|
||||
edgeSpacingRect.Height);
|
||||
}
|
||||
|
||||
g.DrawRectangle(ColorScheme.LayoutOutlinePen,
|
||||
plateRect.X,
|
||||
plateRect.Y,
|
||||
plateRect.Width,
|
||||
plateRect.Height);
|
||||
}
|
||||
|
||||
protected void DrawParts(Graphics g)
|
||||
{
|
||||
var viewBounds = new RectangleF(-origin.X, -origin.Y, Width, Height);
|
||||
|
||||
for (int i = 0; i < parts.Count; ++i)
|
||||
{
|
||||
var part = parts[i];
|
||||
|
||||
if (part.IsDirty)
|
||||
part.Update(this);
|
||||
|
||||
var path = part.Path;
|
||||
var pathBounds = path.GetBounds();
|
||||
|
||||
if (!pathBounds.IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
part.Draw(g, (i + 1).ToString());
|
||||
DrawBendLines(g, part.BasePart);
|
||||
DrawEtchMarks(g, part.BasePart);
|
||||
DrawGrainWarning(g, part.BasePart);
|
||||
}
|
||||
|
||||
// Draw preview parts — active (current strategy) takes precedence
|
||||
// over stationary (overall best) to avoid overlapping fills.
|
||||
var previewParts = activeParts.Count > 0 ? activeParts : stationaryParts;
|
||||
var previewBrush = activeParts.Count > 0 ? ColorScheme.ActivePreviewPartBrush : ColorScheme.PreviewPartBrush;
|
||||
var previewPen = activeParts.Count > 0 ? ColorScheme.ActivePreviewPartPen : ColorScheme.PreviewPartPen;
|
||||
|
||||
for (var i = 0; i < previewParts.Count; i++)
|
||||
{
|
||||
var part = previewParts[i];
|
||||
|
||||
if (part.IsDirty)
|
||||
part.Update(this);
|
||||
|
||||
var path = part.Path;
|
||||
if (!path.GetBounds().IntersectsWith(viewBounds))
|
||||
continue;
|
||||
|
||||
g.FillPath(previewBrush, path);
|
||||
g.DrawPath(previewPen, path);
|
||||
}
|
||||
|
||||
if (DrawOffset && Plate.PartSpacing > 0)
|
||||
DrawOffsetGeometry(g);
|
||||
|
||||
if (DrawBounds)
|
||||
{
|
||||
var bounds = SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox();
|
||||
DrawBox(g, bounds);
|
||||
}
|
||||
|
||||
if (DrawRapid)
|
||||
DrawRapids(g);
|
||||
|
||||
if (DrawPiercePoints)
|
||||
DrawAllPiercePoints(g);
|
||||
}
|
||||
|
||||
private void DrawBendLines(Graphics g, Part part)
|
||||
{
|
||||
if (!ShowBendLines || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
using var bendPen = new Pen(Color.Yellow, 1.5f)
|
||||
{
|
||||
DashStyle = System.Drawing.Drawing2D.DashStyle.Dash
|
||||
};
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
var start = bend.StartPoint;
|
||||
var end = bend.EndPoint;
|
||||
|
||||
// Apply part rotation
|
||||
if (part.Rotation != 0)
|
||||
{
|
||||
start = start.Rotate(part.Rotation);
|
||||
end = end.Rotate(part.Rotation);
|
||||
}
|
||||
|
||||
// Apply part offset
|
||||
start = start + part.Location;
|
||||
end = end + part.Location;
|
||||
|
||||
var pt1 = PointWorldToGraph(start);
|
||||
var pt2 = PointWorldToGraph(end);
|
||||
|
||||
g.DrawLine(bendPen, pt1, pt2);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEtchMarks(Graphics g, Part part)
|
||||
{
|
||||
if (!ShowBendLines || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
using var etchPen = new Pen(Color.Green, 1.5f);
|
||||
var etchLength = 1.0;
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
if (bend.Direction != BendDirection.Up)
|
||||
continue;
|
||||
|
||||
var start = bend.StartPoint;
|
||||
var end = bend.EndPoint;
|
||||
|
||||
// Apply part rotation
|
||||
if (part.Rotation != 0)
|
||||
{
|
||||
start = start.Rotate(part.Rotation);
|
||||
end = end.Rotate(part.Rotation);
|
||||
}
|
||||
|
||||
// Apply part offset
|
||||
start = start + part.Location;
|
||||
end = end + part.Location;
|
||||
|
||||
var length = bend.Length;
|
||||
var angle = bend.StartPoint.AngleTo(bend.EndPoint) + part.Rotation;
|
||||
|
||||
if (length < etchLength * 3.0)
|
||||
{
|
||||
var pt1 = PointWorldToGraph(start);
|
||||
var pt2 = PointWorldToGraph(end);
|
||||
g.DrawLine(etchPen, pt1, pt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dx = System.Math.Cos(angle) * etchLength;
|
||||
var dy = System.Math.Sin(angle) * etchLength;
|
||||
|
||||
var s1 = PointWorldToGraph(start);
|
||||
var e1 = PointWorldToGraph(new Vector(start.X + dx, start.Y + dy));
|
||||
g.DrawLine(etchPen, s1, e1);
|
||||
|
||||
var s2 = PointWorldToGraph(end);
|
||||
var e2 = PointWorldToGraph(new Vector(end.X - dx, end.Y - dy));
|
||||
g.DrawLine(etchPen, s2, e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGrainWarning(Graphics g, Part part)
|
||||
{
|
||||
if (!ShowBendLines || Plate == null || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
var grainAngle = Plate.GrainAngle;
|
||||
var tolerance = Angle.ToRadians(5);
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
var bendAngle = bend.LineAngle + part.Rotation;
|
||||
bendAngle = bendAngle % System.Math.PI;
|
||||
if (bendAngle < 0) bendAngle += System.Math.PI;
|
||||
|
||||
var grainNormalized = grainAngle % System.Math.PI;
|
||||
if (grainNormalized < 0) grainNormalized += System.Math.PI;
|
||||
|
||||
var diff = System.Math.Abs(bendAngle - grainNormalized);
|
||||
diff = System.Math.Min(diff, System.Math.PI - diff);
|
||||
|
||||
if (diff > tolerance)
|
||||
{
|
||||
var box = part.BaseDrawing.Program.BoundingBox();
|
||||
var location = part.Location;
|
||||
var pt1 = PointWorldToGraph(location);
|
||||
var pt2 = PointWorldToGraph(new Vector(
|
||||
location.X + box.Width, location.Y + box.Length));
|
||||
using var warnPen = new Pen(Color.FromArgb(180, 255, 140, 0), 2f);
|
||||
g.DrawRectangle(warnPen, pt1.X, pt2.Y,
|
||||
System.Math.Abs(pt2.X - pt1.X), System.Math.Abs(pt2.Y - pt1.Y));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCutOffs(Graphics g)
|
||||
{
|
||||
if (Plate?.CutOffs == null || Plate.CutOffs.Count == 0)
|
||||
return;
|
||||
|
||||
using var pen = new Pen(Color.FromArgb(64, 64, 64), 1.5f);
|
||||
using var selectedPen = new Pen(Color.FromArgb(0, 120, 255), 3.5f);
|
||||
|
||||
foreach (var cutoff in Plate.CutOffs)
|
||||
{
|
||||
var program = cutoff.Drawing?.Program;
|
||||
if (program == null || program.Codes.Count == 0)
|
||||
continue;
|
||||
|
||||
var activePen = cutoff == selectedCutOff ? selectedPen : pen;
|
||||
|
||||
for (var i = 0; i < program.Codes.Count - 1; i += 2)
|
||||
{
|
||||
if (program.Codes[i] is RapidMove rapid &&
|
||||
program.Codes[i + 1] is LinearMove linear)
|
||||
{
|
||||
DrawLine(g, rapid.EndPoint, linear.EndPoint, activePen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CutOff GetCutOffAtPoint(Vector point, double tolerance)
|
||||
{
|
||||
if (Plate?.CutOffs == null)
|
||||
@@ -798,243 +539,6 @@ namespace OpenNest.Controls
|
||||
return null;
|
||||
}
|
||||
|
||||
private void DrawOffsetGeometry(Graphics g)
|
||||
{
|
||||
using (var offsetPen = new Pen(Color.FromArgb(120, 255, 100, 100)))
|
||||
{
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var layoutPart = parts[i];
|
||||
|
||||
if (layoutPart.IsDirty)
|
||||
layoutPart.Update(this);
|
||||
|
||||
layoutPart.UpdateOffset(Plate.PartSpacing, OffsetTolerance, Matrix);
|
||||
|
||||
if (layoutPart.OffsetPath != null)
|
||||
g.DrawPath(offsetPen, layoutPart.OffsetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRapids(Graphics g)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < Plate.Parts.Count; ++i)
|
||||
{
|
||||
var part = Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
|
||||
// Draw approach rapid directly to the program's first pierce
|
||||
// point instead of to part.Location (the coordinate origin),
|
||||
// which may not be at a cutting feature.
|
||||
var piercePoint = GetFirstPiercePoint(pgm, part.Location);
|
||||
DrawLine(g, pos, piercePoint, ColorScheme.RapidPen);
|
||||
|
||||
pos = part.Location;
|
||||
DrawRapids(g, pgm, ref pos, skipFirstRapid: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector GetFirstPiercePoint(Program pgm, Vector partLocation)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; i++)
|
||||
{
|
||||
if (pgm[i] is Motion motion)
|
||||
{
|
||||
if (pgm.Mode == Mode.Incremental)
|
||||
return motion.EndPoint + partLocation;
|
||||
return motion.EndPoint;
|
||||
}
|
||||
}
|
||||
return partLocation;
|
||||
}
|
||||
|
||||
private void DrawRapids(Graphics g, Program pgm, ref Vector pos, bool skipFirstRapid = false)
|
||||
{
|
||||
var firstRapidSkipped = false;
|
||||
|
||||
for (int i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
var code = pgm[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var program = subpgm.Program;
|
||||
|
||||
if (program != null)
|
||||
DrawRapids(g, program, ref pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
if (pgm.Mode == Mode.Incremental)
|
||||
{
|
||||
var endpt = motion.EndPoint + pos;
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
firstRapidSkipped = true;
|
||||
else
|
||||
DrawLine(g, pos, endpt, ColorScheme.RapidPen);
|
||||
}
|
||||
pos = endpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
if (skipFirstRapid && !firstRapidSkipped)
|
||||
firstRapidSkipped = true;
|
||||
else
|
||||
DrawLine(g, pos, motion.EndPoint, ColorScheme.RapidPen);
|
||||
}
|
||||
pos = motion.EndPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAllPiercePoints(Graphics g)
|
||||
{
|
||||
using var brush = new SolidBrush(Color.Red);
|
||||
using var pen = new Pen(Color.DarkRed, 1f);
|
||||
|
||||
for (var i = 0; i < Plate.Parts.Count; ++i)
|
||||
{
|
||||
var part = Plate.Parts[i];
|
||||
var pgm = part.Program;
|
||||
var pos = part.Location;
|
||||
DrawProgramPiercePoints(g, pgm, ref pos, brush, pen);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProgramPiercePoints(Graphics g, Program pgm, ref Vector pos, Brush brush, Pen pen)
|
||||
{
|
||||
for (var i = 0; i < pgm.Length; ++i)
|
||||
{
|
||||
var code = pgm[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program != null)
|
||||
DrawProgramPiercePoints(g, subpgm.Program, ref pos, brush, pen);
|
||||
}
|
||||
else
|
||||
{
|
||||
var motion = code as Motion;
|
||||
if (motion == null) continue;
|
||||
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint;
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
var pt = PointWorldToGraph(endpt);
|
||||
var radius = 2f;
|
||||
g.FillEllipse(brush, pt.X - radius, pt.Y - radius, radius * 2, radius * 2);
|
||||
g.DrawEllipse(pen, pt.X - radius, pt.Y - radius, radius * 2, radius * 2);
|
||||
}
|
||||
|
||||
pos = endpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLine(Graphics g, Vector pt1, Vector pt2, Pen pen)
|
||||
{
|
||||
var point1 = PointWorldToGraph(pt1);
|
||||
var point2 = PointWorldToGraph(pt2);
|
||||
|
||||
g.DrawLine(pen, point1, point2);
|
||||
}
|
||||
|
||||
private void DrawBox(Graphics g, Box box)
|
||||
{
|
||||
var rect = new RectangleF
|
||||
{
|
||||
Location = PointWorldToGraph(box.Location),
|
||||
Width = LengthWorldToGui(box.Width),
|
||||
Height = LengthWorldToGui(box.Length)
|
||||
};
|
||||
|
||||
g.DrawRectangle(ColorScheme.BoundingBoxPen, rect.X, rect.Y - rect.Height, rect.Width, rect.Height);
|
||||
}
|
||||
|
||||
private void DrawActiveWorkArea(Graphics g)
|
||||
{
|
||||
if (activeWorkArea == null)
|
||||
return;
|
||||
|
||||
var rect = new RectangleF
|
||||
{
|
||||
Location = PointWorldToGraph(activeWorkArea.Location),
|
||||
Width = LengthWorldToGui(activeWorkArea.Width),
|
||||
Height = LengthWorldToGui(activeWorkArea.Length)
|
||||
};
|
||||
rect.Y -= rect.Height;
|
||||
|
||||
using var pen = new Pen(Color.Red, 1.5f)
|
||||
{
|
||||
DashStyle = DashStyle.Dash
|
||||
};
|
||||
g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
|
||||
}
|
||||
|
||||
// Priority 0 = green (preferred), 1 = yellow (extend), 2 = red (last resort)
|
||||
private static readonly Color[] PriorityFills =
|
||||
{
|
||||
Color.FromArgb(60, Color.LimeGreen),
|
||||
Color.FromArgb(60, Color.Gold),
|
||||
Color.FromArgb(60, Color.Salmon),
|
||||
};
|
||||
|
||||
private static readonly Color[] PriorityBorders =
|
||||
{
|
||||
Color.FromArgb(180, Color.Green),
|
||||
Color.FromArgb(180, Color.DarkGoldenrod),
|
||||
Color.FromArgb(180, Color.DarkRed),
|
||||
};
|
||||
|
||||
private void DrawDebugRemnants(Graphics g)
|
||||
{
|
||||
if (debugRemnants == null || debugRemnants.Count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < debugRemnants.Count; i++)
|
||||
{
|
||||
var box = debugRemnants[i];
|
||||
var loc = PointWorldToGraph(box.Location);
|
||||
var w = LengthWorldToGui(box.Width);
|
||||
var h = LengthWorldToGui(box.Length);
|
||||
var rect = new RectangleF(loc.X, loc.Y - h, w, h);
|
||||
|
||||
var priority = DebugRemnantPriorities != null && i < DebugRemnantPriorities.Count
|
||||
? System.Math.Min(DebugRemnantPriorities[i], 2)
|
||||
: 0;
|
||||
|
||||
using var brush = new SolidBrush(PriorityFills[priority]);
|
||||
g.FillRectangle(brush, rect);
|
||||
|
||||
using var pen = new Pen(PriorityBorders[priority], 1.5f);
|
||||
g.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
|
||||
|
||||
var label = $"P{priority} {box.Width:F1}x{box.Length:F1}";
|
||||
using var font = new Font("Segoe UI", 8f);
|
||||
using var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
|
||||
g.DrawString(label, font, Brushes.Black, rect, sf);
|
||||
}
|
||||
}
|
||||
|
||||
public LayoutPart GetPartAtControlPoint(Point pt)
|
||||
{
|
||||
var pt2 = PointControlToGraph(pt);
|
||||
|
||||
@@ -498,6 +498,12 @@ namespace OpenNest.Forms
|
||||
PlateView.Invalidate();
|
||||
}
|
||||
|
||||
public void ToggleCutDirection()
|
||||
{
|
||||
PlateView.DrawCutDirection = !PlateView.DrawCutDirection;
|
||||
PlateView.Invalidate();
|
||||
}
|
||||
|
||||
public void ToggleFillParts()
|
||||
{
|
||||
PlateView.FillParts = !PlateView.FillParts;
|
||||
|
||||
14
OpenNest/Forms/MainForm.Designer.cs
generated
14
OpenNest/Forms/MainForm.Designer.cs
generated
@@ -52,6 +52,7 @@
|
||||
mnuViewDrawPiercePoints = new System.Windows.Forms.ToolStripMenuItem();
|
||||
mnuViewDrawBounds = new System.Windows.Forms.ToolStripMenuItem();
|
||||
mnuViewDrawOffset = new System.Windows.Forms.ToolStripMenuItem();
|
||||
mnuViewDrawCutDirection = new System.Windows.Forms.ToolStripMenuItem();
|
||||
toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator();
|
||||
mnuViewZoomTo = new System.Windows.Forms.ToolStripMenuItem();
|
||||
mnuViewZoomToArea = new System.Windows.Forms.ToolStripMenuItem();
|
||||
@@ -305,7 +306,7 @@
|
||||
//
|
||||
// mnuView
|
||||
//
|
||||
mnuView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuViewDrawRapids, mnuViewDrawPiercePoints, mnuViewDrawBounds, mnuViewDrawOffset, toolStripMenuItem5, mnuViewZoomTo, mnuViewZoomIn, mnuViewZoomOut });
|
||||
mnuView.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { mnuViewDrawRapids, mnuViewDrawPiercePoints, mnuViewDrawBounds, mnuViewDrawOffset, mnuViewDrawCutDirection, toolStripMenuItem5, mnuViewZoomTo, mnuViewZoomIn, mnuViewZoomOut });
|
||||
mnuView.Name = "mnuView";
|
||||
mnuView.Size = new System.Drawing.Size(44, 20);
|
||||
mnuView.Text = "&View";
|
||||
@@ -340,7 +341,15 @@
|
||||
mnuViewDrawOffset.Size = new System.Drawing.Size(222, 22);
|
||||
mnuViewDrawOffset.Text = "Draw Offset";
|
||||
mnuViewDrawOffset.Click += ToggleDrawOffset_Click;
|
||||
//
|
||||
//
|
||||
// mnuViewDrawCutDirection
|
||||
//
|
||||
mnuViewDrawCutDirection.CheckOnClick = true;
|
||||
mnuViewDrawCutDirection.Name = "mnuViewDrawCutDirection";
|
||||
mnuViewDrawCutDirection.Size = new System.Drawing.Size(222, 22);
|
||||
mnuViewDrawCutDirection.Text = "Draw Cut Direction";
|
||||
mnuViewDrawCutDirection.Click += ToggleDrawCutDirection_Click;
|
||||
//
|
||||
// toolStripMenuItem5
|
||||
//
|
||||
toolStripMenuItem5.Name = "toolStripMenuItem5";
|
||||
@@ -1144,6 +1153,7 @@
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuViewDrawPiercePoints;
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuViewDrawBounds;
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuViewDrawOffset;
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuViewDrawCutDirection;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem5;
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuTools;
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuToolsMachineConfig;
|
||||
|
||||
@@ -396,6 +396,7 @@ namespace OpenNest.Forms
|
||||
mnuViewDrawRapids.Checked = activeForm.PlateView.DrawRapid;
|
||||
mnuViewDrawPiercePoints.Checked = activeForm.PlateView.DrawPiercePoints;
|
||||
mnuViewDrawBounds.Checked = activeForm.PlateView.DrawBounds;
|
||||
mnuViewDrawCutDirection.Checked = activeForm.PlateView.DrawCutDirection;
|
||||
statusLabel1.Text = activeForm.PlateView.Status;
|
||||
}
|
||||
|
||||
@@ -585,6 +586,13 @@ namespace OpenNest.Forms
|
||||
mnuViewDrawOffset.Checked = activeForm.PlateView.DrawOffset;
|
||||
}
|
||||
|
||||
private void ToggleDrawCutDirection_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (activeForm == null) return;
|
||||
activeForm.ToggleCutDirection();
|
||||
mnuViewDrawCutDirection.Checked = activeForm.PlateView.DrawCutDirection;
|
||||
}
|
||||
|
||||
private void ZoomToArea_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (activeForm == null) return;
|
||||
|
||||
Reference in New Issue
Block a user