using OpenNest.Actions; using OpenNest.Collections; using OpenNest.Engine.Fill; using OpenNest.Forms; using OpenNest.Geometry; using OpenNest.Math; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Timer = System.Timers.Timer; namespace OpenNest.Controls { public class PlateView : DrawControl { private readonly Timer redrawTimer; private string status; private Plate plate; private ActionManager actionManager; private CutOffSettings cutOffSettings = new CutOffSettings(); private SelectionManager selection; private CutOffHandler cutOffHandler; private PreviewManager previewManager; protected List parts; private Point middleMouseDownPoint; private Box activeWorkArea; private List debugRemnants; private PlateRenderer renderer; private LayoutPart hoveredPart; private Point hoverPoint; private bool showTooltip; private Timer hoverTimer; public Box ActiveWorkArea { get => activeWorkArea; set { activeWorkArea = value; Invalidate(); } } public List DebugRemnants { get => debugRemnants; set { debugRemnants = value; Invalidate(); } } public List DebugRemnantPriorities { get; set; } public List SelectedParts => selection.SelectedParts; public ReadOnlyCollection Parts => new ReadOnlyCollection(parts); internal SelectionManager Selection => selection; internal CutOffHandler CutOffs => cutOffHandler; internal ActionManager Actions => actionManager; internal PreviewManager Previews => previewManager; public event EventHandler> PartAdded; public event EventHandler> PartRemoved; public event EventHandler StatusChanged; public event EventHandler SelectionChanged { add => selection.SelectionChanged += value; remove => selection.SelectionChanged -= value; } public PlateView() : this(ColorScheme.Default) { } public PlateView(ColorScheme colorScheme) { Plate = new Plate(60, 120); origin = new PointF(); parts = new List(); selection = new SelectionManager(this); cutOffHandler = new CutOffHandler(this); previewManager = new PreviewManager(this); redrawTimer = new Timer() { AutoReset = false, Enabled = true, Interval = 50 }; redrawTimer.Elapsed += redrawTimer_Elapsed; hoverTimer = new Timer() { AutoReset = false, Interval = 1000 }; hoverTimer.Elapsed += hoverTimer_Elapsed; SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true); ViewScale = 1.0f; RotateIncrementAngle = 10; OffsetIncrementDistance = 10; ColorScheme = colorScheme; BackColor = colorScheme.BackgroundColor; Cursor = Cursors.Cross; AllowPan = true; AllowSelect = true; AllowZoom = true; AllowDrop = true; DrawOrigin = true; DrawRapid = false; DrawBounds = true; DrawOffset = false; FillParts = true; renderer = new PlateRenderer(this); actionManager = new ActionManager(this); actionManager.SetAction(typeof(ActionSelect)); UpdateMatrix(); } public ColorScheme ColorScheme { get; set; } public bool AllowZoom { get; set; } public bool AllowSelect { get; set; } public bool AllowPan { get; set; } public bool DrawOrigin { get; set; } public bool DrawRapid { get; set; } public bool DrawPiercePoints { get; set; } public bool DrawBounds { get; set; } 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 LayoutParts => parts; internal IReadOnlyList PreviewParts => previewManager.PreviewParts; internal Brush PreviewBrush => previewManager.PreviewBrush; internal Pen PreviewPen => previewManager.PreviewPen; internal RectangleF GetViewBounds() => new RectangleF(-origin.X, -origin.Y, Width, Height); internal PlateRenderer Renderer => renderer; public CutOffSettings CutOffSettings { get => cutOffSettings; set { cutOffSettings = value; Plate?.RegenerateCutOffs(value); Invalidate(); } } public double RotateIncrementAngle { get; set; } public double OffsetIncrementDistance { get; set; } public Plate Plate { get { return plate; } set { SetPlate(value); } } private void SetPlate(Plate p) { if (plate != null) { plate.PartAdded -= plate_PartAdded; plate.PartRemoved -= plate_PartRemoved; parts.Clear(); previewManager.Clear(); selection.Clear(); } plate = p; plate.PartAdded += plate_PartAdded; plate.PartRemoved += plate_PartRemoved; foreach (var part in plate.Parts) parts.Add(LayoutPart.Create(part, this)); actionManager?.OnPlateChanged(); } public string Status { get { return status; } set { status = value; if (StatusChanged != null) StatusChanged.Invoke(this, new EventArgs()); } } protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); } protected override void OnDragEnter(DragEventArgs drgevent) { if (drgevent.Data.GetData(typeof(Drawing)) != null) drgevent.Effect = DragDropEffects.Copy; } protected override void OnDragDrop(DragEventArgs drgevent) { var dwg = drgevent.Data.GetData(typeof(Drawing)) as Drawing; if (dwg == null) return; var pt1 = PointToClient(new Point(drgevent.X, drgevent.Y)); var pt2 = PointControlToWorld(pt1); AddPartFromDrawing(dwg, pt2); } protected override void OnMouseDown(MouseEventArgs e) { if (!Focused) Focus(); if (e.Button == MouseButtons.Middle) middleMouseDownPoint = e.Location; if (e.Button == MouseButtons.Left && actionManager.CurrentAction is ActionSelect) { var hitCutOff = cutOffHandler.TryStartDrag(CurrentPoint, 5.0 / ViewScale); if (hitCutOff != null) { selection.DeselectParts(); selection.SelectedCutOffs.Clear(); selection.SelectedCutOffs.Add(hitCutOff); Invalidate(); return; } else { selection.DeselectCutOffs(); } } base.OnMouseDown(e); } protected override void OnMouseUp(MouseEventArgs e) { if (e.Button == MouseButtons.Middle && SelectedParts.Count > 0) { var dx = e.X - middleMouseDownPoint.X; var dy = e.Y - middleMouseDownPoint.Y; if (dx * dx + dy * dy < 25) { selection.RotateSelectedParts(Angle.ToRadians(90)); Invalidate(); } } if (cutOffHandler.IsDragging && selection.SelectedCutOffs.Count > 0) { cutOffHandler.EndDrag(); return; } base.OnMouseUp(e); } protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); var multiplier = System.Math.Abs(e.Delta / 120); if (SelectedParts.Count > 0 && ((ModifierKeys & Keys.Shift) == Keys.Shift)) { var increment = (ModifierKeys & Keys.Control) == Keys.Control ? RotateIncrementAngle * 0.1 : RotateIncrementAngle; var angle = Angle.ToRadians((e.Delta > 0 ? -increment : increment) * multiplier); selection.RotateSelectedParts(angle); } else { if (AllowZoom) { if (e.Delta > 0) ZoomToControlPoint(e.Location, (float)System.Math.Pow(ZoomInFactor, multiplier)); else ZoomToControlPoint(e.Location, (float)System.Math.Pow(ZoomOutFactor, multiplier)); } } Invalidate(); } protected override void OnMouseMove(MouseEventArgs e) { if (e.Button == MouseButtons.Middle) { if (AllowPan) { var diffx = e.X - lastPoint.X; var diffy = e.Y - lastPoint.Y; origin.X += diffx; origin.Y += diffy; Invalidate(); } } else { LastPoint = CurrentPoint; CurrentPoint = PointControlToWorld(e.Location); } lastPoint = e.Location; if (cutOffHandler.IsDragging && selection.SelectedCutOffs.Count > 0) { cutOffHandler.UpdateDrag(CurrentPoint, selection.SelectedCutOffs[0]); return; } if (e.Button == MouseButtons.None && actionManager.CurrentAction is ActionSelect) { hoverPoint = e.Location; showTooltip = false; hoverTimer.Stop(); hoverTimer.Start(); if (hoveredPart != null) Invalidate(); } else if (hoveredPart != null || showTooltip) { hoveredPart = null; hoverTimer.Stop(); showTooltip = false; Invalidate(); } base.OnMouseMove(e); } protected override void OnMouseDoubleClick(MouseEventArgs e) { base.OnMouseDoubleClick(e); if (e.Button == MouseButtons.Middle && SelectedParts.Count == 0) ZoomToFit(); } protected override void OnKeyDown(KeyEventArgs e) { switch (e.KeyCode) { case Keys.Delete: selection.DeleteSelected(); break; case Keys.F: if ((ModifierKeys & Keys.Control) == 0) ZoomToFit(); else base.OnKeyDown(e); break; default: base.OnKeyDown(e); break; } } public void ProcessEscapeKey() => actionManager.ProcessEscapeKey(); protected override bool ProcessDialogKey(Keys keyData) { // Only handle TAB, RETURN, ESC, and ARROW KEYS here. // All other keys can be handled in OnKeyDown method. switch (keyData) { case Keys.Escape: ProcessEscapeKey(); break; case Keys.Left: SelectedParts.ForEach(part => part.Offset(-OffsetIncrementDistance, 0)); Invalidate(); break; case Keys.X: case Keys.Shift | Keys.Left: selection.PushSelected(PushDirection.Left); break; case Keys.Shift | Keys.X: case Keys.Shift | Keys.Right: selection.PushSelected(PushDirection.Right); break; case Keys.Shift | Keys.Y: case Keys.Shift | Keys.Up: selection.PushSelected(PushDirection.Up); break; case Keys.Y: case Keys.Shift | Keys.Down: selection.PushSelected(PushDirection.Down); break; case Keys.Right: SelectedParts.ForEach(part => part.Offset(OffsetIncrementDistance, 0)); Invalidate(); break; case Keys.Up: SelectedParts.ForEach(part => part.Offset(0, OffsetIncrementDistance)); Invalidate(); break; case Keys.Down: SelectedParts.ForEach(part => part.Offset(0, -OffsetIncrementDistance)); Invalidate(); break; } return base.ProcessDialogKey(keyData); } protected override void OnPaint(PaintEventArgs e) { e.Graphics.SmoothingMode = SmoothingMode.HighSpeed; if (DrawOrigin) { e.Graphics.DrawLine(ColorScheme.OriginPen, origin.X, 0, origin.X, Height); e.Graphics.DrawLine(ColorScheme.OriginPen, 0, origin.Y, Width, origin.Y); } e.Graphics.TranslateTransform(origin.X, origin.Y); renderer.DrawPlate(e.Graphics); renderer.DrawParts(e.Graphics); renderer.DrawCutOffs(e.Graphics); renderer.DrawActiveWorkArea(e.Graphics); renderer.DrawDebugRemnants(e.Graphics); base.OnPaint(e); if (hoveredPart != null && showTooltip) { e.Graphics.ResetTransform(); var text = hoveredPart.BasePart.BaseDrawing.Name; var size = e.Graphics.MeasureString(text, Font); var x = hoverPoint.X + 16f; var y = hoverPoint.Y - size.Height - 6f; if (x + size.Width + 8 > ClientSize.Width) x = hoverPoint.X - size.Width - 8; if (y < 0) y = hoverPoint.Y + 20; var rect = new RectangleF(x, y, size.Width + 6, size.Height + 4); using (var bgBrush = new SolidBrush(Color.FromArgb(230, Color.White))) e.Graphics.FillRectangle(bgBrush, rect); e.Graphics.DrawRectangle(Pens.DimGray, rect.X, rect.Y, rect.Width, rect.Height); e.Graphics.DrawString(text, Font, Brushes.Black, x + 3, y + 2); } } protected override void OnHandleDestroyed(EventArgs e) { base.OnHandleDestroyed(e); actionManager.Cleanup(); } public override void Refresh() { parts.ForEach(p => p.Update(this)); previewManager.Update(); Invalidate(); } public CutOff GetCutOffAtPoint(Vector point, double tolerance) => cutOffHandler.GetCutOffAtPoint(point, tolerance); public LayoutPart GetPartAtControlPoint(Point pt) => selection.GetPartAtControlPoint(pt); public LayoutPart GetPartAtGraphPoint(PointF pt) => selection.GetPartAtGraphPoint(pt); public LayoutPart GetPartAtPoint(Vector pt) => selection.GetPartAtPoint(pt); public IList GetPartsFromWindow(RectangleF rect, SelectionType selectionType) => selection.GetPartsFromWindow(rect, selectionType); public void SetAction(Type type) => actionManager.SetAction(type); public void SetAction(Type type, params object[] args) => actionManager.SetAction(type, args); public void AlignSelected(AlignType alignType) => selection.AlignSelected(alignType); public void AlignSelected(AlignType alignType, LayoutPart fixedPart) => selection.AlignSelected(alignType, fixedPart); public void AddPartFromDrawing(Drawing dwg, Vector location) { var part = new Part(dwg, location); part.Offset( part.Location.X - part.BoundingBox.Center.X, part.Location.Y - part.BoundingBox.Center.Y); Plate.Parts.Add(part); } public void SetStationaryParts(List parts) => previewManager.SetStationaryParts(parts); public void SetActiveParts(List parts) => previewManager.SetActiveParts(parts); public void ClearPreviewParts() => previewManager.ClearPreviewParts(); public void AcceptPreviewParts(List parts) => previewManager.AcceptPreviewParts(parts); public async void FillWithProgress(List groupParts, Box workArea) { var sw = Stopwatch.StartNew(); var cts = new CancellationTokenSource(); var progressForm = new NestProgressForm(cts, showPlateRow: false); var previewPlate = new Plate(Plate.Size) { Quadrant = Plate.Quadrant, PartSpacing = Plate.PartSpacing, }; previewPlate.EdgeSpacing = Plate.EdgeSpacing; progressForm.PreviewPlate = previewPlate; var progress = new Progress(p => { progressForm.UpdateProgress(p); if (p.IsOverallBest) { progressForm.UpdatePreview(p.BestParts); SetActiveParts(p.BestParts); } ActiveWorkArea = p.ActiveWorkArea; }); progressForm.Show(FindForm()); try { var engine = NestEngineRegistry.Create(Plate); var spacing = Plate.PartSpacing; var parts = await Task.Run(() => { var result = engine.Fill(groupParts, workArea, progress, cts.Token); Compactor.Settle(result, workArea, spacing); return result; }); if (parts.Count > 0 && (!cts.IsCancellationRequested || progressForm.Accepted)) { SetActiveParts(parts); AcceptPreviewParts(parts); if (Plate.CutOffs.Count > 0) Plate.RegenerateCutOffs(cutOffSettings); sw.Stop(); Status = $"Fill: {parts.Count} parts in {sw.ElapsedMilliseconds} ms"; } else { ClearPreviewParts(); } progressForm.ShowCompleted(); } catch (Exception) { ClearPreviewParts(); } finally { ActiveWorkArea = null; progressForm.Close(); cts.Dispose(); Focus(); } } public void RemoveSelectedParts() => selection.RemoveSelectedParts(); private void redrawTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (IsDisposed || !IsHandleCreated) return; BeginInvoke(new System.Action(Invalidate)); } private void hoverTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (IsDisposed || !IsHandleCreated) return; BeginInvoke(new System.Action(HoverCheck)); } private void HoverCheck() { var graphPt = PointControlToGraph(hoverPoint); LayoutPart hitPart = null; for (var i = parts.Count - 1; i >= 0; --i) { if (parts[i].Path.GetBounds().Contains(graphPt) && parts[i].Path.IsVisible(graphPt)) { hitPart = parts[i]; break; } } hoveredPart = hitPart; showTooltip = hitPart != null; if (showTooltip) Invalidate(); } private void plate_PartAdded(object sender, ItemAddedEventArgs e) { if (PartAdded != null) PartAdded.Invoke(this, e); parts.Insert(e.Index, LayoutPart.Create(e.Item, this)); redrawTimer.Start(); } private void plate_PartRemoved(object sender, ItemRemovedEventArgs e) { if (PartRemoved != null) PartRemoved.Invoke(this, e); parts.RemoveAll(p => p.BasePart == e.Item); } public void DeselectAll() => selection.DeselectAll(); public void SelectAll() => selection.SelectAll(); public void NotifySelectionChanged() => selection.NotifySelectionChanged(); public override void ZoomToPoint(Vector pt, float zoomFactor, bool redraw = true) { base.ZoomToPoint(pt, zoomFactor, false); if (redraw) Invalidate(); } public override void ZoomToArea(double x, double y, double width, double height, bool redraw = true) { base.ZoomToArea(x, y, width, height, false); if (redraw) Invalidate(); } public virtual void ZoomToFit(bool redraw = true) { ZoomToArea(plate.BoundingBox(true), redraw); } public virtual void ZoomToSelected(bool redraw = true) { ZoomToArea(SelectedParts.Select(p => p.BasePart).ToList().GetBoundingBox(), redraw); } public virtual void ZoomToPlate(bool redraw = true) { ZoomToArea(plate.BoundingBox(false), redraw); } public void PushSelected(PushDirection direction) => selection.PushSelected(direction); public void RotateSelectedParts(double angle) => selection.RotateSelectedParts(angle); protected override void UpdateMatrix() { base.UpdateMatrix(); parts.ForEach(p => p.Update(this)); previewManager.Update(); } } }