Compare commits
7 Commits
d16ef36d34
...
134771aa23
| Author | SHA1 | Date | |
|---|---|---|---|
| 134771aa23 | |||
| 59a66173e1 | |||
| a2b7be44f8 | |||
| e94a556f23 | |||
| 428dbdb03c | |||
| e860ca3f4a | |||
| a399c89f58 |
@@ -11,6 +11,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
var exitPoint = approachPoint;
|
||||
var entities = partProgram.ToGeometry();
|
||||
entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid);
|
||||
|
||||
// Separate scribe/etch entities — they don't get lead-ins or kerf
|
||||
var scribeEntities = entities.FindAll(e => e.Layer == SpecialLayers.Scribe);
|
||||
entities.RemoveAll(e => e.Layer == SpecialLayers.Scribe);
|
||||
|
||||
var profile = new ShapeProfile(entities);
|
||||
|
||||
// Find closest point on perimeter from exit point
|
||||
@@ -21,10 +27,23 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterPoint);
|
||||
orderedCutouts.Reverse();
|
||||
|
||||
// Build output program: cutouts first (farthest to nearest), perimeter last
|
||||
var result = new Program();
|
||||
// Build output program: scribe first, cutouts second, perimeter last
|
||||
var result = new Program(Mode.Absolute);
|
||||
var currentPoint = exitPoint;
|
||||
|
||||
// Emit scribe/etch contours first (no lead-ins, no kerf)
|
||||
if (scribeEntities.Count > 0)
|
||||
{
|
||||
var scribeShapes = ShapeBuilder.GetShapes(scribeEntities);
|
||||
foreach (var scribe in scribeShapes)
|
||||
{
|
||||
var startPt = GetShapeStartPoint(scribe);
|
||||
result.Codes.Add(new RapidMove(startPt));
|
||||
result.Codes.AddRange(ConvertShapeToMoves(scribe, startPt, LayerType.Scribe));
|
||||
currentPoint = startPt;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var cutout in orderedCutouts)
|
||||
{
|
||||
var contourType = DetectContourType(cutout);
|
||||
@@ -35,6 +54,9 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var leadIn = SelectLeadIn(contourType);
|
||||
var leadOut = SelectLeadOut(contourType);
|
||||
|
||||
if (contourType == ContourType.ArcCircle && entity is Circle circle)
|
||||
leadIn = ClampLeadInForCircle(leadIn, circle, closestPt, normal);
|
||||
|
||||
result.Codes.AddRange(leadIn.Generate(closestPt, normal, winding));
|
||||
var reindexed = cutout.ReindexAt(closestPt, entity);
|
||||
result.Codes.AddRange(ConvertShapeToMoves(reindexed, closestPt));
|
||||
@@ -152,6 +174,50 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return area >= 0 ? RotationType.CCW : RotationType.CW;
|
||||
}
|
||||
|
||||
private LeadIn ClampLeadInForCircle(LeadIn leadIn, Circle circle, Vector contourPoint, double normalAngle)
|
||||
{
|
||||
if (leadIn is NoLeadIn || Parameters.PierceClearance <= 0)
|
||||
return leadIn;
|
||||
|
||||
var piercePoint = leadIn.GetPiercePoint(contourPoint, normalAngle);
|
||||
var maxRadius = circle.Radius - Parameters.PierceClearance;
|
||||
if (maxRadius <= 0)
|
||||
return leadIn;
|
||||
|
||||
var distFromCenter = piercePoint.DistanceTo(circle.Center);
|
||||
if (distFromCenter <= maxRadius)
|
||||
return leadIn;
|
||||
|
||||
// Compute max distance from contourPoint toward piercePoint that stays
|
||||
// inside a circle of radius maxRadius centered at circle.Center.
|
||||
// Solve: |contourPoint + t*d - center|^2 = maxRadius^2
|
||||
var currentDist = contourPoint.DistanceTo(piercePoint);
|
||||
if (currentDist < Math.Tolerance.Epsilon)
|
||||
return leadIn;
|
||||
|
||||
var dx = (piercePoint.X - contourPoint.X) / currentDist;
|
||||
var dy = (piercePoint.Y - contourPoint.Y) / currentDist;
|
||||
var vx = contourPoint.X - circle.Center.X;
|
||||
var vy = contourPoint.Y - circle.Center.Y;
|
||||
|
||||
var b = 2.0 * (vx * dx + vy * dy);
|
||||
var c = vx * vx + vy * vy - maxRadius * maxRadius;
|
||||
var discriminant = b * b - 4.0 * c;
|
||||
|
||||
if (discriminant < 0)
|
||||
return leadIn;
|
||||
|
||||
var t = (-b + System.Math.Sqrt(discriminant)) / 2.0;
|
||||
if (t <= 0)
|
||||
return leadIn;
|
||||
|
||||
var scale = t / currentDist;
|
||||
if (scale >= 1.0)
|
||||
return leadIn;
|
||||
|
||||
return leadIn.Scale(scale);
|
||||
}
|
||||
|
||||
private LeadIn SelectLeadIn(ContourType contourType)
|
||||
{
|
||||
return contourType switch
|
||||
@@ -172,7 +238,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
};
|
||||
}
|
||||
|
||||
private List<ICode> ConvertShapeToMoves(Shape shape, Vector startPoint)
|
||||
private List<ICode> ConvertShapeToMoves(Shape shape, Vector startPoint, LayerType layer = LayerType.Display)
|
||||
{
|
||||
var moves = new List<ICode>();
|
||||
|
||||
@@ -180,15 +246,15 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
if (entity is Line line)
|
||||
{
|
||||
moves.Add(new LinearMove(line.EndPoint));
|
||||
moves.Add(new LinearMove(line.EndPoint) { Layer = layer });
|
||||
}
|
||||
else if (entity is Arc arc)
|
||||
{
|
||||
moves.Add(new ArcMove(arc.EndPoint(), arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW));
|
||||
moves.Add(new ArcMove(arc.EndPoint(), arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW) { Layer = layer });
|
||||
}
|
||||
else if (entity is Circle circle)
|
||||
{
|
||||
moves.Add(new ArcMove(startPoint, circle.Center, circle.Rotation));
|
||||
moves.Add(new ArcMove(startPoint, circle.Center, circle.Rotation) { Layer = layer });
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -198,5 +264,14 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private static Vector GetShapeStartPoint(Shape shape)
|
||||
{
|
||||
var first = shape.Entities[0];
|
||||
if (first is Line line) return line.StartPoint;
|
||||
if (first is Arc arc) return arc.StartPoint();
|
||||
if (first is Circle circle) return new Vector(circle.Center.X + circle.Radius, circle.Center.Y);
|
||||
return Vector.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn();
|
||||
public LeadOut ArcCircleLeadOut { get; set; } = new NoLeadOut();
|
||||
|
||||
public double PierceClearance { get; set; } = 0.0625;
|
||||
|
||||
public Tab TabConfig { get; set; }
|
||||
public bool TabsEnabled { get; set; }
|
||||
|
||||
|
||||
@@ -32,5 +32,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
arcCenterX + Radius * System.Math.Cos(contourNormalAngle),
|
||||
arcCenterY + Radius * System.Math.Sin(contourNormalAngle));
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new ArcLeadIn { Radius = Radius * factor };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +45,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
arcStartX + LineLength * System.Math.Cos(lineAngle),
|
||||
arcStartY + LineLength * System.Math.Sin(lineAngle));
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new CleanHoleLeadIn { LineLength = LineLength * factor, ArcRadius = ArcRadius * factor, Kerf = Kerf };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
RotationType winding = RotationType.CW);
|
||||
|
||||
public abstract Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle);
|
||||
|
||||
public virtual LeadIn Scale(double factor) => this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +45,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
arcStartX + LineLength * System.Math.Cos(lineAngle),
|
||||
arcStartY + LineLength * System.Math.Sin(lineAngle));
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new LineArcLeadIn { LineLength = LineLength * factor, ArcRadius = ArcRadius * factor, ApproachAngle = ApproachAngle };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
contourStartPoint.X + Length * System.Math.Cos(approachAngle),
|
||||
contourStartPoint.Y + Length * System.Math.Sin(approachAngle));
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new LineLeadIn { Length = Length * factor, ApproachAngle = ApproachAngle };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,5 +40,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
midX + Length1 * System.Math.Cos(firstAngle),
|
||||
midY + Length1 * System.Math.Sin(firstAngle));
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new LineLineLeadIn { Length1 = Length1 * factor, ApproachAngle1 = ApproachAngle1, Length2 = Length2 * factor, ApproachAngle2 = ApproachAngle2 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace OpenNest
|
||||
/// </summary>
|
||||
public double Rotation
|
||||
{
|
||||
get { return Program.Rotation; }
|
||||
get { return HasManualLeadIns ? preLeadInRotation : Program.Rotation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace OpenNest.Engine
|
||||
}
|
||||
|
||||
// Pierce point: program start point in plate space
|
||||
var pierceLocal = GetProgramStartPoint(part.Program);
|
||||
var pierceLocal = GetProgramStartPoint(processedProgram);
|
||||
var piercePoint = ToPlateSpace(pierceLocal, part);
|
||||
|
||||
// Plan rapid from currentPoint to pierce point
|
||||
|
||||
@@ -2,6 +2,7 @@ using OpenNest.CNC.CuttingStrategy;
|
||||
using OpenNest.Controls;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
@@ -148,6 +149,35 @@ namespace OpenNest.Actions
|
||||
if (leadIn == null)
|
||||
return;
|
||||
|
||||
// Clamp lead-in for circle contours so it stays inside the hole
|
||||
if (snapContourType == ContourType.ArcCircle && snapEntity is Circle snapCircle
|
||||
&& parameters.PierceClearance > 0)
|
||||
{
|
||||
var pierceCheck = leadIn.GetPiercePoint(snapPoint, snapNormal);
|
||||
var distFromCenter = pierceCheck.DistanceTo(snapCircle.Center);
|
||||
var maxRadius = snapCircle.Radius - parameters.PierceClearance;
|
||||
if (maxRadius > 0 && distFromCenter > maxRadius)
|
||||
{
|
||||
var currentDist = snapPoint.DistanceTo(pierceCheck);
|
||||
if (currentDist > Tolerance.Epsilon)
|
||||
{
|
||||
var dx = (pierceCheck.X - snapPoint.X) / currentDist;
|
||||
var dy = (pierceCheck.Y - snapPoint.Y) / currentDist;
|
||||
var vx = snapPoint.X - snapCircle.Center.X;
|
||||
var vy = snapPoint.Y - snapCircle.Center.Y;
|
||||
var b = 2.0 * (vx * dx + vy * dy);
|
||||
var c = vx * vx + vy * vy - maxRadius * maxRadius;
|
||||
var disc = b * b - 4.0 * c;
|
||||
if (disc >= 0)
|
||||
{
|
||||
var t = (-b + System.Math.Sqrt(disc)) / 2.0;
|
||||
if (t > 0 && t < currentDist)
|
||||
leadIn = leadIn.Scale(t / currentDist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the pierce point (in local space)
|
||||
var piercePoint = leadIn.GetPiercePoint(snapPoint, snapNormal);
|
||||
var worldPierce = TransformToWorld(piercePoint);
|
||||
@@ -219,6 +249,7 @@ namespace OpenNest.Actions
|
||||
}
|
||||
|
||||
var entities = ConvertProgram.ToGeometry(cleanProgram);
|
||||
entities.RemoveAll(e => e.Layer == SpecialLayers.Scribe);
|
||||
profile = new ShapeProfile(entities);
|
||||
|
||||
contours = new List<ShapeInfo>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-490
@@ -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,212 +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;
|
||||
|
||||
DrawLine(g, pos, part.Location, ColorScheme.RapidPen);
|
||||
pos = part.Location;
|
||||
DrawRapids(g, pgm, ref pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRapids(Graphics g, Program pgm, ref Vector pos)
|
||||
{
|
||||
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)
|
||||
DrawLine(g, pos, endpt, ColorScheme.RapidPen);
|
||||
pos = endpt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
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);
|
||||
|
||||
+2
-2
@@ -114,7 +114,7 @@ namespace OpenNest.Forms
|
||||
this.bottomPanel.Controls.Add(this.acceptButton);
|
||||
this.bottomPanel.Controls.Add(this.cancelButton);
|
||||
this.bottomPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
this.bottomPanel.Location = new System.Drawing.Point(0, 406);
|
||||
this.bottomPanel.Location = new System.Drawing.Point(0, 466);
|
||||
this.bottomPanel.Name = "bottomPanel";
|
||||
this.bottomPanel.Size = new System.Drawing.Size(380, 50);
|
||||
this.bottomPanel.TabIndex = 1;
|
||||
@@ -125,7 +125,7 @@ namespace OpenNest.Forms
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelButton;
|
||||
this.ClientSize = new System.Drawing.Size(380, 456);
|
||||
this.ClientSize = new System.Drawing.Size(380, 516);
|
||||
this.Controls.Add(this.tabControl);
|
||||
this.Controls.Add(this.bottomPanel);
|
||||
this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
|
||||
@@ -22,8 +22,20 @@ namespace OpenNest.Forms
|
||||
|
||||
private CheckBox chkTabsEnabled;
|
||||
private NumericUpDown nudTabWidth;
|
||||
private NumericUpDown nudPierceClearance;
|
||||
|
||||
public CuttingParameters Parameters { get; set; } = new CuttingParameters();
|
||||
private bool hasCustomParameters;
|
||||
private CuttingParameters parameters = new CuttingParameters();
|
||||
|
||||
public CuttingParameters Parameters
|
||||
{
|
||||
get => parameters;
|
||||
set
|
||||
{
|
||||
parameters = value;
|
||||
hasCustomParameters = true;
|
||||
}
|
||||
}
|
||||
|
||||
public CuttingParametersForm()
|
||||
{
|
||||
@@ -54,9 +66,33 @@ namespace OpenNest.Forms
|
||||
protected override void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
|
||||
// If caller didn't provide custom parameters, try loading saved ones
|
||||
if (!hasCustomParameters)
|
||||
{
|
||||
var json = Properties.Settings.Default.CuttingParametersJson;
|
||||
if (!string.IsNullOrEmpty(json))
|
||||
{
|
||||
try { Parameters = CuttingParametersSerializer.Deserialize(json); }
|
||||
catch { /* use defaults on corrupt data */ }
|
||||
}
|
||||
}
|
||||
|
||||
LoadFromParameters(Parameters);
|
||||
}
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
base.OnFormClosing(e);
|
||||
|
||||
if (DialogResult == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
var json = CuttingParametersSerializer.Serialize(BuildParameters());
|
||||
Properties.Settings.Default.CuttingParametersJson = json;
|
||||
Properties.Settings.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupTab(TabPage tab,
|
||||
out ComboBox leadInCombo, out Panel leadInPanel,
|
||||
out ComboBox leadOutCombo, out Panel leadOutPanel)
|
||||
@@ -164,6 +200,35 @@ namespace OpenNest.Forms
|
||||
grpTabs.Controls.Add(nudTabWidth);
|
||||
|
||||
Controls.Add(grpTabs);
|
||||
|
||||
var grpPierce = new GroupBox
|
||||
{
|
||||
Text = "Pierce",
|
||||
Location = new System.Drawing.Point(4, 410),
|
||||
Size = new System.Drawing.Size(372, 55),
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right
|
||||
};
|
||||
|
||||
grpPierce.Controls.Add(new Label
|
||||
{
|
||||
Text = "Pierce Clearance:",
|
||||
Location = new System.Drawing.Point(12, 23),
|
||||
AutoSize = true
|
||||
});
|
||||
|
||||
nudPierceClearance = new NumericUpDown
|
||||
{
|
||||
Location = new System.Drawing.Point(130, 20),
|
||||
Size = new System.Drawing.Size(100, 22),
|
||||
DecimalPlaces = 4,
|
||||
Increment = 0.0625m,
|
||||
Minimum = 0,
|
||||
Maximum = 9999,
|
||||
Value = 0.0625m
|
||||
};
|
||||
grpPierce.Controls.Add(nudPierceClearance);
|
||||
|
||||
Controls.Add(grpPierce);
|
||||
}
|
||||
|
||||
private void PopulateDropdowns()
|
||||
@@ -305,6 +370,8 @@ namespace OpenNest.Forms
|
||||
chkTabsEnabled.Checked = p.TabsEnabled;
|
||||
if (p.TabConfig != null)
|
||||
nudTabWidth.Value = (decimal)p.TabConfig.Size;
|
||||
|
||||
nudPierceClearance.Value = (decimal)p.PierceClearance;
|
||||
}
|
||||
|
||||
private static void LoadLeadIn(ComboBox combo, Panel panel, LeadIn leadIn)
|
||||
@@ -379,7 +446,8 @@ namespace OpenNest.Forms
|
||||
ArcCircleLeadIn = BuildLeadIn(cboArcCircleLeadIn, pnlArcCircleLeadIn),
|
||||
ArcCircleLeadOut = BuildLeadOut(cboArcCircleLeadOut, pnlArcCircleLeadOut),
|
||||
TabsEnabled = chkTabsEnabled.Checked,
|
||||
TabConfig = new NormalTab { Size = (double)nudTabWidth.Value }
|
||||
TabConfig = new NormalTab { Size = (double)nudTabWidth.Value },
|
||||
PierceClearance = (double)nudPierceClearance.Value
|
||||
};
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using OpenNest.CNC.CuttingStrategy;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace OpenNest.Forms
|
||||
{
|
||||
internal static class CuttingParametersSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = false,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
public static string Serialize(CuttingParameters p)
|
||||
{
|
||||
var dto = new CuttingParametersDto
|
||||
{
|
||||
ExternalLeadIn = ToDto(p.ExternalLeadIn),
|
||||
ExternalLeadOut = ToLeadOutDto(p.ExternalLeadOut),
|
||||
InternalLeadIn = ToDto(p.InternalLeadIn),
|
||||
InternalLeadOut = ToLeadOutDto(p.InternalLeadOut),
|
||||
ArcCircleLeadIn = ToDto(p.ArcCircleLeadIn),
|
||||
ArcCircleLeadOut = ToLeadOutDto(p.ArcCircleLeadOut),
|
||||
TabsEnabled = p.TabsEnabled,
|
||||
TabWidth = p.TabConfig?.Size ?? 0.25,
|
||||
PierceClearance = p.PierceClearance
|
||||
};
|
||||
return JsonSerializer.Serialize(dto, JsonOptions);
|
||||
}
|
||||
|
||||
public static CuttingParameters Deserialize(string json)
|
||||
{
|
||||
var dto = JsonSerializer.Deserialize<CuttingParametersDto>(json, JsonOptions);
|
||||
if (dto == null)
|
||||
return new CuttingParameters();
|
||||
|
||||
return new CuttingParameters
|
||||
{
|
||||
ExternalLeadIn = FromDto(dto.ExternalLeadIn),
|
||||
ExternalLeadOut = FromLeadOutDto(dto.ExternalLeadOut),
|
||||
InternalLeadIn = FromDto(dto.InternalLeadIn),
|
||||
InternalLeadOut = FromLeadOutDto(dto.InternalLeadOut),
|
||||
ArcCircleLeadIn = FromDto(dto.ArcCircleLeadIn),
|
||||
ArcCircleLeadOut = FromLeadOutDto(dto.ArcCircleLeadOut),
|
||||
TabsEnabled = dto.TabsEnabled,
|
||||
TabConfig = new NormalTab { Size = dto.TabWidth },
|
||||
PierceClearance = dto.PierceClearance
|
||||
};
|
||||
}
|
||||
|
||||
private static LeadInDto ToDto(LeadIn leadIn)
|
||||
{
|
||||
return leadIn switch
|
||||
{
|
||||
LineLeadIn line => new LeadInDto { Type = "Line", Length = line.Length, ApproachAngle = line.ApproachAngle },
|
||||
ArcLeadIn arc => new LeadInDto { Type = "Arc", Radius = arc.Radius },
|
||||
LineArcLeadIn la => new LeadInDto { Type = "LineArc", LineLength = la.LineLength, ArcRadius = la.ArcRadius, ApproachAngle = la.ApproachAngle },
|
||||
CleanHoleLeadIn ch => new LeadInDto { Type = "CleanHole", LineLength = ch.LineLength, ArcRadius = ch.ArcRadius, Kerf = ch.Kerf },
|
||||
LineLineLeadIn ll => new LeadInDto { Type = "LineLine", Length1 = ll.Length1, Angle1 = ll.ApproachAngle1, Length2 = ll.Length2, Angle2 = ll.ApproachAngle2 },
|
||||
_ => new LeadInDto { Type = "None" }
|
||||
};
|
||||
}
|
||||
|
||||
private static LeadIn FromDto(LeadInDto dto)
|
||||
{
|
||||
if (dto == null) return new NoLeadIn();
|
||||
return dto.Type switch
|
||||
{
|
||||
"Line" => new LineLeadIn { Length = dto.Length, ApproachAngle = dto.ApproachAngle },
|
||||
"Arc" => new ArcLeadIn { Radius = dto.Radius },
|
||||
"LineArc" => new LineArcLeadIn { LineLength = dto.LineLength, ArcRadius = dto.ArcRadius, ApproachAngle = dto.ApproachAngle },
|
||||
"CleanHole" => new CleanHoleLeadIn { LineLength = dto.LineLength, ArcRadius = dto.ArcRadius, Kerf = dto.Kerf },
|
||||
"LineLine" => new LineLineLeadIn { Length1 = dto.Length1, ApproachAngle1 = dto.Angle1, Length2 = dto.Length2, ApproachAngle2 = dto.Angle2 },
|
||||
_ => new NoLeadIn()
|
||||
};
|
||||
}
|
||||
|
||||
private static LeadOutDto ToLeadOutDto(LeadOut leadOut)
|
||||
{
|
||||
return leadOut switch
|
||||
{
|
||||
LineLeadOut line => new LeadOutDto { Type = "Line", Length = line.Length, ApproachAngle = line.ApproachAngle },
|
||||
ArcLeadOut arc => new LeadOutDto { Type = "Arc", Radius = arc.Radius },
|
||||
MicrotabLeadOut mt => new LeadOutDto { Type = "Microtab", GapSize = mt.GapSize },
|
||||
_ => new LeadOutDto { Type = "None" }
|
||||
};
|
||||
}
|
||||
|
||||
private static LeadOut FromLeadOutDto(LeadOutDto dto)
|
||||
{
|
||||
if (dto == null) return new NoLeadOut();
|
||||
return dto.Type switch
|
||||
{
|
||||
"Line" => new LineLeadOut { Length = dto.Length, ApproachAngle = dto.ApproachAngle },
|
||||
"Arc" => new ArcLeadOut { Radius = dto.Radius },
|
||||
"Microtab" => new MicrotabLeadOut { GapSize = dto.GapSize },
|
||||
_ => new NoLeadOut()
|
||||
};
|
||||
}
|
||||
|
||||
private class CuttingParametersDto
|
||||
{
|
||||
public LeadInDto ExternalLeadIn { get; set; }
|
||||
public LeadOutDto ExternalLeadOut { get; set; }
|
||||
public LeadInDto InternalLeadIn { get; set; }
|
||||
public LeadOutDto InternalLeadOut { get; set; }
|
||||
public LeadInDto ArcCircleLeadIn { get; set; }
|
||||
public LeadOutDto ArcCircleLeadOut { get; set; }
|
||||
public bool TabsEnabled { get; set; }
|
||||
public double TabWidth { get; set; }
|
||||
public double PierceClearance { get; set; }
|
||||
}
|
||||
|
||||
private class LeadInDto
|
||||
{
|
||||
public string Type { get; set; } = "None";
|
||||
public double Length { get; set; }
|
||||
public double ApproachAngle { get; set; }
|
||||
public double Radius { get; set; }
|
||||
public double LineLength { get; set; }
|
||||
public double ArcRadius { get; set; }
|
||||
public double Kerf { get; set; }
|
||||
public double Length1 { get; set; }
|
||||
public double Angle1 { get; set; }
|
||||
public double Length2 { get; set; }
|
||||
public double Angle2 { get; set; }
|
||||
}
|
||||
|
||||
private class LeadOutDto
|
||||
{
|
||||
public string Type { get; set; } = "None";
|
||||
public double Length { get; set; }
|
||||
public double ApproachAngle { get; set; }
|
||||
public double Radius { get; set; }
|
||||
public double GapSize { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+13
-1
@@ -40,6 +40,7 @@
|
||||
this.toolStripButton1 = new System.Windows.Forms.ToolStripButton();
|
||||
this.btnAssignLeadIns = new System.Windows.Forms.ToolStripButton();
|
||||
this.btnPlaceLeadIn = new System.Windows.Forms.ToolStripButton();
|
||||
this.btnRemoveLeadIns = new System.Windows.Forms.ToolStripButton();
|
||||
this.tabPage2 = new System.Windows.Forms.TabPage();
|
||||
this.drawingListBox1 = new OpenNest.Controls.DrawingListBox();
|
||||
this.toolStrip2 = new System.Windows.Forms.ToolStrip();
|
||||
@@ -137,7 +138,8 @@
|
||||
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.toolStripButton1,
|
||||
this.btnAssignLeadIns,
|
||||
this.btnPlaceLeadIn});
|
||||
this.btnPlaceLeadIn,
|
||||
this.btnRemoveLeadIns});
|
||||
this.toolStrip1.Location = new System.Drawing.Point(3, 3);
|
||||
this.toolStrip1.Name = "toolStrip1";
|
||||
this.toolStrip1.Size = new System.Drawing.Size(227, 31);
|
||||
@@ -174,6 +176,15 @@
|
||||
this.btnPlaceLeadIn.Text = "Place Lead-in";
|
||||
this.btnPlaceLeadIn.Click += new System.EventHandler(this.PlaceLeadIn_Click);
|
||||
//
|
||||
// btnRemoveLeadIns
|
||||
//
|
||||
this.btnRemoveLeadIns.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.btnRemoveLeadIns.ImageTransparentColor = System.Drawing.Color.Magenta;
|
||||
this.btnRemoveLeadIns.Name = "btnRemoveLeadIns";
|
||||
this.btnRemoveLeadIns.Size = new System.Drawing.Size(104, 28);
|
||||
this.btnRemoveLeadIns.Text = "Remove Lead-ins";
|
||||
this.btnRemoveLeadIns.Click += new System.EventHandler(this.RemoveLeadIns_Click);
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
this.tabPage2.Controls.Add(this.drawingListBox1);
|
||||
@@ -290,5 +301,6 @@
|
||||
private System.Windows.Forms.ToolStripButton toolStripButton3;
|
||||
private System.Windows.Forms.ToolStripButton btnAssignLeadIns;
|
||||
private System.Windows.Forms.ToolStripButton btnPlaceLeadIn;
|
||||
private System.Windows.Forms.ToolStripButton btnRemoveLeadIns;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -738,6 +744,38 @@ namespace OpenNest.Forms
|
||||
PlateView.Invalidate();
|
||||
}
|
||||
|
||||
private void RemoveLeadIns_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (PlateView?.Plate == null)
|
||||
return;
|
||||
|
||||
var plate = PlateView.Plate;
|
||||
var count = 0;
|
||||
|
||||
foreach (var part in plate.Parts)
|
||||
{
|
||||
if (part.HasManualLeadIns)
|
||||
{
|
||||
part.RemoveLeadIns();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
plate.CuttingParameters = null;
|
||||
|
||||
// Rebuild all layout part graphics
|
||||
foreach (var lp in PlateView.Parts)
|
||||
{
|
||||
lp.IsDirty = true;
|
||||
lp.Update();
|
||||
}
|
||||
|
||||
PlateView.Invalidate();
|
||||
}
|
||||
|
||||
private void PlaceLeadIn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (PlateView?.Plate == null)
|
||||
|
||||
Generated
+11
-1
@@ -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";
|
||||
@@ -341,6 +342,14 @@
|
||||
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;
|
||||
|
||||
+56
-14
@@ -135,6 +135,8 @@ namespace OpenNest
|
||||
break;
|
||||
|
||||
case CodeType.RapidMove:
|
||||
cutPath.StartFigure();
|
||||
leadPath.StartFigure();
|
||||
AddLine(cutPath, (RapidMove)code, mode, ref curpos);
|
||||
break;
|
||||
|
||||
@@ -203,12 +205,6 @@ namespace OpenNest
|
||||
size, size,
|
||||
(float)startAngle,
|
||||
(float)sweepAngle);
|
||||
|
||||
if (arc.Layer == LayerType.Leadin || arc.Layer == LayerType.Leadout)
|
||||
{
|
||||
path.AddArc(pt.X, pt.Y, size, size, (float)(-startAngle + sweepAngle), (float)-sweepAngle);
|
||||
path.CloseFigure();
|
||||
}
|
||||
}
|
||||
|
||||
curpos = endpt;
|
||||
@@ -226,9 +222,6 @@ namespace OpenNest
|
||||
|
||||
path.AddLine(pt1, pt2);
|
||||
|
||||
if (line.Layer == LayerType.Leadin || line.Layer == LayerType.Leadout)
|
||||
path.CloseFigure();
|
||||
|
||||
curpos = pt;
|
||||
}
|
||||
|
||||
@@ -239,14 +232,23 @@ namespace OpenNest
|
||||
if (mode == Mode.Incremental)
|
||||
pt += curpos;
|
||||
|
||||
path.CloseFigure();
|
||||
|
||||
curpos = pt;
|
||||
}
|
||||
|
||||
private static void AddProgram(GraphicsPath path, Program pgm, Mode mode, ref Vector 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)
|
||||
{
|
||||
@@ -255,25 +257,54 @@ namespace OpenNest
|
||||
switch (code.Type)
|
||||
{
|
||||
case CodeType.ArcMove:
|
||||
AddArc(path, (ArcMove)code, mode, ref curpos);
|
||||
{
|
||||
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:
|
||||
AddLine(path, (LinearMove)code, mode, ref curpos);
|
||||
{
|
||||
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)
|
||||
{
|
||||
path.StartFigure();
|
||||
AddProgram(path, subpgm.Program, mode, ref curpos);
|
||||
}
|
||||
|
||||
@@ -282,6 +313,8 @@ namespace OpenNest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
||||
private static void AddArc(GraphicsPath path, Arc arc)
|
||||
@@ -331,6 +364,15 @@ namespace OpenNest
|
||||
{
|
||||
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:
|
||||
|
||||
Generated
+12
@@ -226,5 +226,17 @@ namespace OpenNest.Properties {
|
||||
this["DisabledStrategies"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string CuttingParametersJson {
|
||||
get {
|
||||
return ((string)(this["CuttingParametersJson"]));
|
||||
}
|
||||
set {
|
||||
this["CuttingParametersJson"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,5 +53,8 @@
|
||||
<Setting Name="DisabledStrategies" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="CuttingParametersJson" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
||||
@@ -56,6 +56,9 @@
|
||||
<setting name="LastPierceTime" serializeAs="String">
|
||||
<value>0</value>
|
||||
</setting>
|
||||
<setting name="CuttingParametersJson" serializeAs="String">
|
||||
<value/>
|
||||
</setting>
|
||||
</OpenNest.Properties.Settings>
|
||||
<OpenNest.Resources.Settings>
|
||||
<setting name="MainFormLocation" serializeAs="String">
|
||||
|
||||
Reference in New Issue
Block a user