0e05ad04ea
Remove dead programIdFont field, unused imports (OpenNest.CNC, System.ComponentModel, OpenNest.Math, System.Collections.ObjectModel). PlateView is now 692 lines (down from 1035). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
217 lines
6.8 KiB
C#
217 lines
6.8 KiB
C#
using OpenNest.Engine.Fill;
|
|
using OpenNest.Geometry;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
|
|
namespace OpenNest.Controls
|
|
{
|
|
internal class SelectionManager
|
|
{
|
|
private readonly PlateView view;
|
|
private readonly List<LayoutPart> selectedParts = new List<LayoutPart>();
|
|
private readonly List<CutOff> selectedCutOffs = new List<CutOff>();
|
|
|
|
public SelectionManager(PlateView view)
|
|
{
|
|
this.view = view;
|
|
}
|
|
|
|
public List<LayoutPart> SelectedParts => selectedParts;
|
|
public List<CutOff> SelectedCutOffs => selectedCutOffs;
|
|
|
|
public event EventHandler SelectionChanged;
|
|
|
|
public void DeselectAll()
|
|
{
|
|
selectedParts.ForEach(p => p.IsSelected = false);
|
|
selectedParts.Clear();
|
|
selectedCutOffs.Clear();
|
|
SelectionChanged?.Invoke(view, EventArgs.Empty);
|
|
}
|
|
|
|
public void DeselectParts()
|
|
{
|
|
selectedParts.ForEach(p => p.IsSelected = false);
|
|
selectedParts.Clear();
|
|
SelectionChanged?.Invoke(view, EventArgs.Empty);
|
|
}
|
|
|
|
public void DeselectCutOffs()
|
|
{
|
|
selectedCutOffs.Clear();
|
|
view.Invalidate();
|
|
}
|
|
|
|
public void SelectAll()
|
|
{
|
|
var parts = view.LayoutParts;
|
|
parts.ForEach(p => p.IsSelected = true);
|
|
selectedParts.AddRange(parts);
|
|
SelectionChanged?.Invoke(view, EventArgs.Empty);
|
|
}
|
|
|
|
public void NotifySelectionChanged()
|
|
{
|
|
SelectionChanged?.Invoke(view, EventArgs.Empty);
|
|
}
|
|
|
|
public void DeleteSelected()
|
|
{
|
|
if (selectedCutOffs.Count > 0)
|
|
{
|
|
foreach (var cutOff in selectedCutOffs)
|
|
view.Plate.CutOffs.Remove(cutOff);
|
|
|
|
selectedCutOffs.Clear();
|
|
view.Plate.RegenerateCutOffs(view.CutOffSettings);
|
|
view.Invalidate();
|
|
}
|
|
else
|
|
{
|
|
RemoveSelectedParts();
|
|
}
|
|
}
|
|
|
|
public void RemoveSelectedParts()
|
|
{
|
|
foreach (var part in selectedParts)
|
|
view.Plate.Parts.Remove(part.BasePart);
|
|
|
|
DeselectAll();
|
|
view.Invalidate();
|
|
}
|
|
|
|
public void AlignSelected(AlignType alignType)
|
|
{
|
|
if (selectedParts.Count == 0)
|
|
return;
|
|
|
|
AlignSelected(alignType, selectedParts[0]);
|
|
}
|
|
|
|
public void AlignSelected(AlignType alignType, LayoutPart fixedPart)
|
|
{
|
|
switch (alignType)
|
|
{
|
|
case AlignType.Bottom:
|
|
Align.Bottom(fixedPart.BasePart, selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.Horizontally:
|
|
Align.Horizontally(fixedPart.BasePart, selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.Left:
|
|
Align.Left(fixedPart.BasePart, selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.Right:
|
|
Align.Right(fixedPart.BasePart, selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.Top:
|
|
Align.Top(fixedPart.BasePart, selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.Vertically:
|
|
Align.Vertically(fixedPart.BasePart, selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.EvenlySpaceHorizontally:
|
|
Align.EvenlyDistributeHorizontally(selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
case AlignType.EvenlySpaceVertically:
|
|
Align.EvenlyDistributeVertically(selectedParts.Select(p => p.BasePart).ToList());
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
selectedParts.ForEach(p => p.IsDirty = true);
|
|
view.Invalidate();
|
|
}
|
|
|
|
public void RotateSelectedParts(double angle)
|
|
{
|
|
var parts = selectedParts.Select(p => p.BasePart).ToList();
|
|
var bounds = parts.GetBoundingBox();
|
|
var center = bounds.Center;
|
|
var anchor = bounds.Location;
|
|
|
|
for (var i = 0; i < selectedParts.Count; ++i)
|
|
selectedParts[i].BasePart.Rotate(angle, center);
|
|
|
|
var diff = anchor - parts.GetBoundingBox().Location;
|
|
|
|
for (var i = 0; i < selectedParts.Count; ++i)
|
|
selectedParts[i].Offset(diff);
|
|
|
|
if (view.Plate.CutOffs.Count > 0)
|
|
view.Plate.RegenerateCutOffs(view.CutOffSettings);
|
|
}
|
|
|
|
public void PushSelected(PushDirection direction)
|
|
{
|
|
var movingParts = selectedParts.Select(p => p.BasePart).ToList();
|
|
Compactor.Push(movingParts, view.Plate, direction);
|
|
selectedParts.ForEach(p => p.IsDirty = true);
|
|
view.Invalidate();
|
|
}
|
|
|
|
public LayoutPart GetPartAtControlPoint(Point pt)
|
|
{
|
|
var pt2 = view.PointControlToGraph(pt);
|
|
return GetPartAtGraphPoint(pt2);
|
|
}
|
|
|
|
public LayoutPart GetPartAtGraphPoint(PointF pt)
|
|
{
|
|
var parts = view.LayoutParts;
|
|
for (var i = parts.Count - 1; i >= 0; --i)
|
|
{
|
|
if (parts[i].Path.IsVisible(pt))
|
|
return parts[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public LayoutPart GetPartAtPoint(Vector pt)
|
|
{
|
|
var pt2 = view.PointWorldToGraph(pt);
|
|
return GetPartAtGraphPoint(pt2);
|
|
}
|
|
|
|
public IList<LayoutPart> GetPartsFromWindow(RectangleF rect, SelectionType selectionType)
|
|
{
|
|
var list = new List<LayoutPart>();
|
|
var parts = view.LayoutParts;
|
|
|
|
if (selectionType == SelectionType.Intersect)
|
|
{
|
|
for (var i = 0; i < parts.Count; ++i)
|
|
{
|
|
var part = parts[i];
|
|
var region = new Region(part.Path);
|
|
if (region.IsVisible(rect))
|
|
list.Add(part);
|
|
region.Dispose();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (var i = 0; i < parts.Count; ++i)
|
|
{
|
|
var part = parts[i];
|
|
var bounds = part.Path.GetBounds();
|
|
if (rect.Contains(bounds))
|
|
list.Add(part);
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
selectedParts.Clear();
|
|
selectedCutOffs.Clear();
|
|
}
|
|
}
|
|
}
|