mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-07 01:22:11 -04:00
Mirrors PushSelected: PlateView/SelectionManager.ExpandSelected runs Expander.Expand on the selection, marks parts dirty, regenerates cut offs, and reports achieved spacing plus blocked-pair count in the status bar. Menu item joins Align Selected's enable surface.
260 lines
8.1 KiB
C#
260 lines
8.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using OpenNest.Engine.Fill;
|
|
using OpenNest.Geometry;
|
|
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Grows the part-to-part spacing of the selection as far as the plate
|
|
/// allows (Expander). Returns false when nothing could be done.
|
|
/// </summary>
|
|
public bool ExpandSelected()
|
|
{
|
|
var movingParts = selectedParts.Select(p => p.BasePart).ToList();
|
|
|
|
if (movingParts.Count < 2)
|
|
return false;
|
|
|
|
var result = Expander.Expand(movingParts, view.Plate);
|
|
|
|
if (result.Cancelled)
|
|
return false;
|
|
|
|
selectedParts.ForEach(p => p.IsDirty = true);
|
|
|
|
if (view.Plate.CutOffs.Count > 0)
|
|
view.Plate.RegenerateCutOffs(view.CutOffSettings);
|
|
|
|
view.Invalidate();
|
|
|
|
view.Status =
|
|
$"Expanded spacing to {result.AchievedSpacing:0.###}"
|
|
+ (result.Violations.Count > 0 ? $" - {result.Violations.Count} blocked pairs" : "");
|
|
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|