feat: unify ActionAddPart into ActionClone and add group fill support
Merge ActionAddPart into ActionClone by adding a Drawing constructor, eliminating the redundant class. ActionClone now handles both adding new parts from a drawing and cloning selected part groups. Added Ctrl+F fill support for groups using FillLinear pattern tiling, and adopted quadrant-aware push directions from ActionAddPart. Refactored FillLinear to extract shared helpers and add a Fill(Pattern) overload for tiling arbitrary part groups across the work area. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,14 @@ Nest files (`.zip`) contain:
|
|||||||
- `program-NNN` — G-code text for each drawing's cut program
|
- `program-NNN` — G-code text for each drawing's cut program
|
||||||
- `plate-NNN` — G-code text encoding part placements (G00 for position, G65 for sub-program call with rotation)
|
- `plate-NNN` — G-code text encoding part placements (G00 for position, G65 for sub-program call with rotation)
|
||||||
|
|
||||||
|
## Tool Preferences
|
||||||
|
|
||||||
|
Always use Roslyn Bridge MCP tools (`mcp__RoslynBridge__*`) as the primary method for exploring and analyzing this codebase. It is faster and more efficient than file-based searches. Use it for finding symbols, references, diagnostics, type hierarchies, and code navigation. Only fall back to Glob/Grep when Roslyn Bridge cannot fulfill the query.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Always use `var` instead of explicit types (e.g., `var parts = new List<Part>();` not `List<Part> parts = new List<Part>();`).
|
||||||
|
|
||||||
## Key Patterns
|
## Key Patterns
|
||||||
|
|
||||||
- OpenNest.Core uses multiple namespaces: `OpenNest` (root domain), `OpenNest.CNC`, `OpenNest.Geometry`, `OpenNest.Converters`, `OpenNest.Math`, `OpenNest.Collections`.
|
- OpenNest.Core uses multiple namespaces: `OpenNest` (root domain), `OpenNest.CNC`, `OpenNest.Geometry`, `OpenNest.Converters`, `OpenNest.Math`, `OpenNest.Collections`.
|
||||||
|
|||||||
+141
-134
@@ -16,80 +16,146 @@ namespace OpenNest
|
|||||||
|
|
||||||
public double PartSpacing { get; }
|
public double PartSpacing { get; }
|
||||||
|
|
||||||
|
private static Vector MakeOffset(NestDirection direction, double distance)
|
||||||
|
{
|
||||||
|
return direction == NestDirection.Horizontal
|
||||||
|
? new Vector(distance, 0)
|
||||||
|
: new Vector(0, distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PushDirection GetPushDirection(NestDirection direction)
|
||||||
|
{
|
||||||
|
return direction == NestDirection.Horizontal
|
||||||
|
? PushDirection.Left
|
||||||
|
: PushDirection.Down;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetDimension(Box box, NestDirection direction)
|
||||||
|
{
|
||||||
|
return direction == NestDirection.Horizontal ? box.Width : box.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetStart(Box box, NestDirection direction)
|
||||||
|
{
|
||||||
|
return direction == NestDirection.Horizontal ? box.Left : box.Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetLimit(NestDirection direction)
|
||||||
|
{
|
||||||
|
return direction == NestDirection.Horizontal ? WorkArea.Right : WorkArea.Top;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NestDirection PerpendicularAxis(NestDirection direction)
|
||||||
|
{
|
||||||
|
return direction == NestDirection.Horizontal
|
||||||
|
? NestDirection.Vertical
|
||||||
|
: NestDirection.Horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the slide distance for the push algorithm, returning the
|
||||||
|
/// geometry-aware copy distance along the given axis.
|
||||||
|
/// </summary>
|
||||||
|
private double ComputeCopyDistance(double bboxDim, double slideDistance)
|
||||||
|
{
|
||||||
|
if (slideDistance >= double.MaxValue || slideDistance < 0)
|
||||||
|
return bboxDim + PartSpacing;
|
||||||
|
|
||||||
|
return bboxDim - slideDistance + PartSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the geometry-aware copy distance between two identical parts along an axis.
|
/// Finds the geometry-aware copy distance between two identical parts along an axis.
|
||||||
/// Places part B at bounding box offset from part A, then pushes B back toward A
|
|
||||||
/// using directional distance to find the tightest non-overlapping position.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private double FindCopyDistance(Part partA, NestDirection direction)
|
private double FindCopyDistance(Part partA, NestDirection direction)
|
||||||
{
|
{
|
||||||
var bbox = partA.BoundingBox;
|
var bboxDim = GetDimension(partA.BoundingBox, direction);
|
||||||
double bboxDim;
|
var pushDir = GetPushDirection(direction);
|
||||||
PushDirection pushDir;
|
|
||||||
Vector copyOffset;
|
|
||||||
|
|
||||||
if (direction == NestDirection.Horizontal)
|
|
||||||
{
|
|
||||||
bboxDim = bbox.Width;
|
|
||||||
pushDir = PushDirection.Left;
|
|
||||||
copyOffset = new Vector(bboxDim, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bboxDim = bbox.Height;
|
|
||||||
pushDir = PushDirection.Down;
|
|
||||||
copyOffset = new Vector(0, bboxDim);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create part B offset by bounding box dimension (guaranteed no overlap).
|
|
||||||
var partB = (Part)partA.Clone();
|
var partB = (Part)partA.Clone();
|
||||||
partB.Offset(copyOffset);
|
partB.Offset(MakeOffset(direction, bboxDim));
|
||||||
|
|
||||||
// Get geometry lines for push calculation.
|
|
||||||
var opposite = Helper.OppositeDirection(pushDir);
|
var opposite = Helper.OppositeDirection(pushDir);
|
||||||
|
var movingLines = Helper.GetPartLines(partB, pushDir);
|
||||||
var movingLines = PartSpacing > 0
|
|
||||||
? Helper.GetOffsetPartLines(partB, PartSpacing, pushDir)
|
|
||||||
: Helper.GetPartLines(partB, pushDir);
|
|
||||||
|
|
||||||
var stationaryLines = Helper.GetPartLines(partA, opposite);
|
var stationaryLines = Helper.GetPartLines(partA, opposite);
|
||||||
|
|
||||||
// Find how far B can slide toward A.
|
|
||||||
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
|
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
|
||||||
|
|
||||||
if (slideDistance >= double.MaxValue || slideDistance < 0)
|
return ComputeCopyDistance(bboxDim, slideDistance);
|
||||||
return bboxDim;
|
}
|
||||||
|
|
||||||
return bboxDim - slideDistance;
|
/// <summary>
|
||||||
|
/// Finds the geometry-aware copy distance between two identical patterns along an axis.
|
||||||
|
/// </summary>
|
||||||
|
private double FindPatternCopyDistance(Pattern patternA, NestDirection direction)
|
||||||
|
{
|
||||||
|
var bboxDim = GetDimension(patternA.BoundingBox, direction);
|
||||||
|
var pushDir = GetPushDirection(direction);
|
||||||
|
|
||||||
|
var patternB = patternA.Clone(MakeOffset(direction, bboxDim));
|
||||||
|
|
||||||
|
var opposite = Helper.OppositeDirection(pushDir);
|
||||||
|
var movingLines = patternB.GetLines(pushDir);
|
||||||
|
var stationaryLines = patternA.GetLines(opposite);
|
||||||
|
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
|
||||||
|
|
||||||
|
return ComputeCopyDistance(bboxDim, slideDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tiles a pattern along the given axis, returning the cloned parts
|
||||||
|
/// (does not include the original pattern's parts).
|
||||||
|
/// </summary>
|
||||||
|
private List<Part> TilePattern(Pattern basePattern, NestDirection direction)
|
||||||
|
{
|
||||||
|
var result = new List<Part>();
|
||||||
|
var copyDistance = FindPatternCopyDistance(basePattern, direction);
|
||||||
|
|
||||||
|
if (copyDistance <= 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var dim = GetDimension(basePattern.BoundingBox, direction);
|
||||||
|
var start = GetStart(basePattern.BoundingBox, direction);
|
||||||
|
var limit = GetLimit(direction);
|
||||||
|
|
||||||
|
var count = 1;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var nextPos = start + copyDistance * count;
|
||||||
|
|
||||||
|
if (nextPos + dim > limit + Tolerance.Epsilon)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var clone = basePattern.Clone(MakeOffset(direction, copyDistance * count));
|
||||||
|
result.AddRange(clone.Parts);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fills a single row of identical parts along one axis using geometry-aware spacing.
|
/// Fills a single row of identical parts along one axis using geometry-aware spacing.
|
||||||
/// Returns a Pattern containing all placed parts.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Pattern FillRow(Drawing drawing, double rotationAngle, NestDirection direction)
|
public Pattern FillRow(Drawing drawing, double rotationAngle, NestDirection direction)
|
||||||
{
|
{
|
||||||
var pattern = new Pattern();
|
var pattern = new Pattern();
|
||||||
|
|
||||||
// Create the template part with rotation applied.
|
|
||||||
var template = new Part(drawing);
|
var template = new Part(drawing);
|
||||||
|
|
||||||
if (!rotationAngle.IsEqualTo(0))
|
if (!rotationAngle.IsEqualTo(0))
|
||||||
template.Rotate(rotationAngle);
|
template.Rotate(rotationAngle);
|
||||||
|
|
||||||
// Position template at work area origin.
|
|
||||||
var bbox = template.Program.BoundingBox();
|
var bbox = template.Program.BoundingBox();
|
||||||
template.Offset(WorkArea.Location - bbox.Location);
|
template.Offset(WorkArea.Location - bbox.Location);
|
||||||
template.UpdateBounds();
|
template.UpdateBounds();
|
||||||
|
|
||||||
// Check if the part fits in the work area at all.
|
|
||||||
if (template.BoundingBox.Width > WorkArea.Width + Tolerance.Epsilon ||
|
if (template.BoundingBox.Width > WorkArea.Width + Tolerance.Epsilon ||
|
||||||
template.BoundingBox.Height > WorkArea.Height + Tolerance.Epsilon)
|
template.BoundingBox.Height > WorkArea.Height + Tolerance.Epsilon)
|
||||||
return pattern;
|
return pattern;
|
||||||
|
|
||||||
pattern.Parts.Add(template);
|
pattern.Parts.Add(template);
|
||||||
|
|
||||||
// Find the geometry-aware copy distance.
|
|
||||||
var copyDistance = FindCopyDistance(template, direction);
|
var copyDistance = FindCopyDistance(template, direction);
|
||||||
|
|
||||||
if (copyDistance <= 0)
|
if (copyDistance <= 0)
|
||||||
@@ -98,35 +164,22 @@ namespace OpenNest
|
|||||||
return pattern;
|
return pattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill the row by copying at the fixed interval.
|
var dim = GetDimension(template.BoundingBox, direction);
|
||||||
double limit = direction == NestDirection.Horizontal
|
var start = GetStart(template.BoundingBox, direction);
|
||||||
? WorkArea.Right
|
var limit = GetLimit(direction);
|
||||||
: WorkArea.Top;
|
|
||||||
|
|
||||||
double partDim = direction == NestDirection.Horizontal
|
var count = 1;
|
||||||
? template.BoundingBox.Width
|
|
||||||
: template.BoundingBox.Height;
|
|
||||||
|
|
||||||
int count = 1;
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
double nextPos = (direction == NestDirection.Horizontal
|
var nextPos = start + copyDistance * count;
|
||||||
? template.BoundingBox.Left
|
|
||||||
: template.BoundingBox.Bottom) + copyDistance * count;
|
|
||||||
|
|
||||||
// Check if the next part would exceed the work area.
|
if (nextPos + dim > limit + Tolerance.Epsilon)
|
||||||
if (nextPos + partDim > limit + Tolerance.Epsilon)
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
var offset = direction == NestDirection.Horizontal
|
|
||||||
? new Vector(copyDistance * count, 0)
|
|
||||||
: new Vector(0, copyDistance * count);
|
|
||||||
|
|
||||||
var clone = (Part)template.Clone();
|
var clone = (Part)template.Clone();
|
||||||
clone.Offset(offset);
|
clone.Offset(MakeOffset(direction, copyDistance * count));
|
||||||
pattern.Parts.Add(clone);
|
pattern.Parts.Add(clone);
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,45 +188,41 @@ namespace OpenNest
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the geometry-aware copy distance between two identical patterns along an axis.
|
/// Fills the work area by tiling a pre-built pattern along both axes.
|
||||||
/// Same push algorithm as FindCopyDistance but operates on pattern line groups.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private double FindPatternCopyDistance(Pattern patternA, NestDirection direction)
|
public List<Part> Fill(Pattern pattern, NestDirection primaryAxis)
|
||||||
{
|
{
|
||||||
var bbox = patternA.BoundingBox;
|
var result = new List<Part>();
|
||||||
double bboxDim;
|
|
||||||
PushDirection pushDir;
|
|
||||||
Vector copyOffset;
|
|
||||||
|
|
||||||
if (direction == NestDirection.Horizontal)
|
if (pattern.Parts.Count == 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var offset = WorkArea.Location - pattern.BoundingBox.Location;
|
||||||
|
var basePattern = pattern.Clone(offset);
|
||||||
|
|
||||||
|
if (basePattern.BoundingBox.Width > WorkArea.Width + Tolerance.Epsilon ||
|
||||||
|
basePattern.BoundingBox.Height > WorkArea.Height + Tolerance.Epsilon)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result.AddRange(basePattern.Parts);
|
||||||
|
|
||||||
|
// Tile along the primary axis.
|
||||||
|
var primaryTiles = TilePattern(basePattern, primaryAxis);
|
||||||
|
result.AddRange(primaryTiles);
|
||||||
|
|
||||||
|
// Build a full-row pattern for perpendicular tiling.
|
||||||
|
if (primaryTiles.Count > 0)
|
||||||
{
|
{
|
||||||
bboxDim = bbox.Width;
|
var rowPattern = new Pattern();
|
||||||
pushDir = PushDirection.Left;
|
rowPattern.Parts.AddRange(result);
|
||||||
copyOffset = new Vector(bboxDim, 0);
|
rowPattern.UpdateBounds();
|
||||||
}
|
basePattern = rowPattern;
|
||||||
else
|
|
||||||
{
|
|
||||||
bboxDim = bbox.Height;
|
|
||||||
pushDir = PushDirection.Down;
|
|
||||||
copyOffset = new Vector(0, bboxDim);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var patternB = patternA.Clone(copyOffset);
|
// Tile along the perpendicular axis.
|
||||||
|
result.AddRange(TilePattern(basePattern, PerpendicularAxis(primaryAxis)));
|
||||||
|
|
||||||
var opposite = Helper.OppositeDirection(pushDir);
|
return result;
|
||||||
|
|
||||||
var movingLines = PartSpacing > 0
|
|
||||||
? patternB.GetOffsetLines(PartSpacing, pushDir)
|
|
||||||
: patternB.GetLines(pushDir);
|
|
||||||
|
|
||||||
var stationaryLines = patternA.GetLines(opposite);
|
|
||||||
|
|
||||||
var slideDistance = Helper.DirectionalDistance(movingLines, stationaryLines, pushDir);
|
|
||||||
|
|
||||||
if (slideDistance >= double.MaxValue || slideDistance < 0)
|
|
||||||
return bboxDim;
|
|
||||||
|
|
||||||
return bboxDim - slideDistance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -182,55 +231,13 @@ namespace OpenNest
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<Part> Fill(Drawing drawing, double rotationAngle, NestDirection primaryAxis)
|
public List<Part> Fill(Drawing drawing, double rotationAngle, NestDirection primaryAxis)
|
||||||
{
|
{
|
||||||
var result = new List<Part>();
|
|
||||||
|
|
||||||
// Step 1: Build the row pattern along the primary axis.
|
|
||||||
var rowPattern = FillRow(drawing, rotationAngle, primaryAxis);
|
var rowPattern = FillRow(drawing, rotationAngle, primaryAxis);
|
||||||
|
|
||||||
if (rowPattern.Parts.Count == 0)
|
if (rowPattern.Parts.Count == 0)
|
||||||
return result;
|
return new List<Part>();
|
||||||
|
|
||||||
// Add the first row.
|
var result = new List<Part>(rowPattern.Parts);
|
||||||
result.AddRange(rowPattern.Parts);
|
result.AddRange(TilePattern(rowPattern, PerpendicularAxis(primaryAxis)));
|
||||||
|
|
||||||
// Step 2: Tile the row pattern along the perpendicular axis.
|
|
||||||
var perpAxis = primaryAxis == NestDirection.Horizontal
|
|
||||||
? NestDirection.Vertical
|
|
||||||
: NestDirection.Horizontal;
|
|
||||||
|
|
||||||
var copyDistance = FindPatternCopyDistance(rowPattern, perpAxis);
|
|
||||||
|
|
||||||
if (copyDistance <= 0)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
double limit = perpAxis == NestDirection.Horizontal
|
|
||||||
? WorkArea.Right
|
|
||||||
: WorkArea.Top;
|
|
||||||
|
|
||||||
double patternDim = perpAxis == NestDirection.Horizontal
|
|
||||||
? rowPattern.BoundingBox.Width
|
|
||||||
: rowPattern.BoundingBox.Height;
|
|
||||||
|
|
||||||
int count = 1;
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
double nextPos = (perpAxis == NestDirection.Horizontal
|
|
||||||
? rowPattern.BoundingBox.Left
|
|
||||||
: rowPattern.BoundingBox.Bottom) + copyDistance * count;
|
|
||||||
|
|
||||||
if (nextPos + patternDim > limit + Tolerance.Epsilon)
|
|
||||||
break;
|
|
||||||
|
|
||||||
var offset = perpAxis == NestDirection.Horizontal
|
|
||||||
? new Vector(copyDistance * count, 0)
|
|
||||||
: new Vector(0, copyDistance * count);
|
|
||||||
|
|
||||||
var clone = rowPattern.Clone(offset);
|
|
||||||
result.AddRange(clone.Parts);
|
|
||||||
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,46 @@ namespace OpenNest
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Fill(List<Part> groupParts)
|
||||||
|
{
|
||||||
|
if (groupParts == null || groupParts.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var workArea = Plate.WorkArea();
|
||||||
|
var engine = new FillLinear(workArea, Plate.PartSpacing);
|
||||||
|
|
||||||
|
// Build a pattern from the group of parts.
|
||||||
|
var pattern = new Pattern();
|
||||||
|
foreach (var part in groupParts)
|
||||||
|
{
|
||||||
|
var clone = (Part)part.Clone();
|
||||||
|
clone.UpdateBounds();
|
||||||
|
pattern.Parts.Add(clone);
|
||||||
|
}
|
||||||
|
pattern.UpdateBounds();
|
||||||
|
|
||||||
|
// Try 4 configurations: 2 axes x 2 orientations (horizontal/vertical).
|
||||||
|
var configs = new[]
|
||||||
|
{
|
||||||
|
engine.Fill(pattern, NestDirection.Horizontal),
|
||||||
|
engine.Fill(pattern, NestDirection.Vertical)
|
||||||
|
};
|
||||||
|
|
||||||
|
List<Part> best = null;
|
||||||
|
|
||||||
|
foreach (var config in configs)
|
||||||
|
{
|
||||||
|
if (best == null || config.Count > best.Count)
|
||||||
|
best = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best == null || best.Count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Plate.Parts.AddRange(best);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Fill(NestItem item, int maxCount)
|
public bool Fill(NestItem item, int maxCount)
|
||||||
{
|
{
|
||||||
if (maxCount <= 0)
|
if (maxCount <= 0)
|
||||||
@@ -169,12 +209,17 @@ namespace OpenNest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert to polygon so arcs are properly represented as line segments.
|
||||||
|
// Shape.FindBestRotation() uses Entity cardinal points which are incorrect
|
||||||
|
// for arcs that don't sweep through all 4 cardinal directions.
|
||||||
|
var polygon = largest.ToPolygonWithTolerance(0.1);
|
||||||
|
|
||||||
BoundingRectangleResult result;
|
BoundingRectangleResult result;
|
||||||
|
|
||||||
if (item.RotationStart.IsEqualTo(0) && item.RotationEnd.IsEqualTo(0))
|
if (item.RotationStart.IsEqualTo(0) && item.RotationEnd.IsEqualTo(0))
|
||||||
result = largest.FindBestRotation();
|
result = polygon.FindBestRotation();
|
||||||
else
|
else
|
||||||
result = largest.FindBestRotation(item.RotationStart, item.RotationEnd);
|
result = polygon.FindBestRotation(item.RotationStart, item.RotationEnd);
|
||||||
|
|
||||||
// Negate the angle to align the minimum bounding rectangle with the axes.
|
// Negate the angle to align the minimum bounding rectangle with the axes.
|
||||||
return -result.Angle;
|
return -result.Angle;
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
using OpenNest.Controls;
|
|
||||||
using OpenNest.Geometry;
|
|
||||||
|
|
||||||
namespace OpenNest.Actions
|
|
||||||
{
|
|
||||||
[DisplayName("Add Parts")]
|
|
||||||
public class ActionAddPart : Action
|
|
||||||
{
|
|
||||||
private LayoutPart part;
|
|
||||||
private double lastScale;
|
|
||||||
|
|
||||||
public ActionAddPart(PlateView plateView)
|
|
||||||
: this(plateView, null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ActionAddPart(PlateView plateView, Drawing drawing)
|
|
||||||
: base(plateView)
|
|
||||||
{
|
|
||||||
plateView.KeyDown += plateView_KeyDown;
|
|
||||||
plateView.MouseMove += plateView_MouseMove;
|
|
||||||
plateView.MouseDown += plateView_MouseDown;
|
|
||||||
plateView.Paint += plateView_Paint;
|
|
||||||
|
|
||||||
part = LayoutPart.Create(new Part(drawing), plateView);
|
|
||||||
part.IsSelected = true;
|
|
||||||
|
|
||||||
lastScale = double.NaN;
|
|
||||||
|
|
||||||
plateView.SelectedParts.Clear();
|
|
||||||
plateView.SelectedParts.Add(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void plateView_MouseDown(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
switch (e.Button)
|
|
||||||
{
|
|
||||||
case MouseButtons.Left:
|
|
||||||
Apply();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void plateView_KeyDown(object sender, KeyEventArgs e)
|
|
||||||
{
|
|
||||||
switch (e.KeyCode)
|
|
||||||
{
|
|
||||||
case Keys.F1:
|
|
||||||
case Keys.Enter:
|
|
||||||
Apply();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Keys.F:
|
|
||||||
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
|
|
||||||
Fill();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void plateView_Paint(object sender, PaintEventArgs e)
|
|
||||||
{
|
|
||||||
if (plateView.ViewScale != lastScale)
|
|
||||||
{
|
|
||||||
part.Update(plateView);
|
|
||||||
part.Draw(e.Graphics);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (part.IsDirty)
|
|
||||||
part.Update(plateView);
|
|
||||||
|
|
||||||
part.Draw(e.Graphics);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastScale = plateView.ViewScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void plateView_MouseMove(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
var offset = plateView.CurrentPoint - part.BoundingBox.Location;
|
|
||||||
part.Offset(offset);
|
|
||||||
plateView.Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DisconnectEvents()
|
|
||||||
{
|
|
||||||
plateView.KeyDown -= plateView_KeyDown;
|
|
||||||
plateView.MouseMove -= plateView_MouseMove;
|
|
||||||
plateView.MouseDown -= plateView_MouseDown;
|
|
||||||
plateView.Paint -= plateView_Paint;
|
|
||||||
|
|
||||||
plateView.SelectedParts.Clear();
|
|
||||||
plateView.Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CancelAction()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsBusy()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Fill()
|
|
||||||
{
|
|
||||||
var boxes = new List<Box>();
|
|
||||||
|
|
||||||
foreach (var part in plateView.Plate.Parts)
|
|
||||||
boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing));
|
|
||||||
|
|
||||||
var bounds = plateView.Plate.WorkArea();
|
|
||||||
|
|
||||||
var vbox = Helper.GetLargestBoxVertically(plateView.CurrentPoint, bounds, boxes);
|
|
||||||
var hbox = Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, bounds, boxes);
|
|
||||||
|
|
||||||
var box = vbox.Area() > hbox.Area() ? vbox : hbox;
|
|
||||||
|
|
||||||
var engine = new NestEngine(plateView.Plate);
|
|
||||||
engine.FillArea(box, new NestItem { Drawing = this.part.BasePart.BaseDrawing });
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Apply()
|
|
||||||
{
|
|
||||||
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
|
|
||||||
{
|
|
||||||
switch (plateView.Plate.Quadrant)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
plateView.PushSelected(PushDirection.Left);
|
|
||||||
plateView.PushSelected(PushDirection.Down);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
plateView.PushSelected(PushDirection.Right);
|
|
||||||
plateView.PushSelected(PushDirection.Down);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
plateView.PushSelected(PushDirection.Right);
|
|
||||||
plateView.PushSelected(PushDirection.Up);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
plateView.PushSelected(PushDirection.Left);
|
|
||||||
plateView.PushSelected(PushDirection.Up);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
plateView.Plate.Parts.Add(part.BasePart.Clone() as Part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using OpenNest.Controls;
|
using OpenNest.Controls;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
@@ -13,6 +14,11 @@ namespace OpenNest.Actions
|
|||||||
|
|
||||||
private double lastScale;
|
private double lastScale;
|
||||||
|
|
||||||
|
public ActionClone(PlateView plateView, Drawing drawing)
|
||||||
|
: this(plateView, new List<Part> { new Part(drawing) })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public ActionClone(PlateView plateView, List<Part> partsToClone)
|
public ActionClone(PlateView plateView, List<Part> partsToClone)
|
||||||
: base(plateView)
|
: base(plateView)
|
||||||
{
|
{
|
||||||
@@ -53,6 +59,11 @@ namespace OpenNest.Actions
|
|||||||
case Keys.Enter:
|
case Keys.Enter:
|
||||||
Apply();
|
Apply();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Keys.F:
|
||||||
|
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
|
||||||
|
Fill();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +122,38 @@ namespace OpenNest.Actions
|
|||||||
{
|
{
|
||||||
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
|
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
|
||||||
{
|
{
|
||||||
plateView.PushSelected(PushDirection.Left);
|
switch (plateView.Plate.Quadrant)
|
||||||
plateView.PushSelected(PushDirection.Down);
|
{
|
||||||
|
case 1:
|
||||||
|
plateView.PushSelected(PushDirection.Left);
|
||||||
|
plateView.PushSelected(PushDirection.Down);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
plateView.PushSelected(PushDirection.Right);
|
||||||
|
plateView.PushSelected(PushDirection.Down);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
plateView.PushSelected(PushDirection.Right);
|
||||||
|
plateView.PushSelected(PushDirection.Up);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
plateView.PushSelected(PushDirection.Left);
|
||||||
|
plateView.PushSelected(PushDirection.Up);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parts.ForEach(p => plateView.Plate.Parts.Add(p.BasePart.Clone() as Part));
|
parts.ForEach(p => plateView.Plate.Parts.Add(p.BasePart.Clone() as Part));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Fill()
|
||||||
|
{
|
||||||
|
var engine = new NestEngine(plateView.Plate);
|
||||||
|
var groupParts = parts.Select(p => p.BasePart).ToList();
|
||||||
|
engine.Fill(groupParts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -687,7 +687,7 @@ namespace OpenNest.Forms
|
|||||||
if (drawing == null)
|
if (drawing == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
PlateView.SetAction(typeof(ActionAddPart), drawing);
|
PlateView.SetAction(typeof(ActionClone), drawing);
|
||||||
|
|
||||||
addPart = false;
|
addPart = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Actions\Action.cs" />
|
<Compile Include="Actions\Action.cs" />
|
||||||
<Compile Include="Actions\ActionAddPart.cs" />
|
|
||||||
<Compile Include="Actions\ActionClone.cs" />
|
<Compile Include="Actions\ActionClone.cs" />
|
||||||
<Compile Include="Actions\ActionFillArea.cs" />
|
<Compile Include="Actions\ActionFillArea.cs" />
|
||||||
<Compile Include="Actions\ActionSelect.cs" />
|
<Compile Include="Actions\ActionSelect.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user