Compare commits
5 Commits
ba88ac253a
...
9cba3a6cd7
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cba3a6cd7 | |||
| e93523d7a2 | |||
| 3bdbf21881 | |||
| a8e42fb4b5 | |||
| ea3c6afbdd |
@@ -104,6 +104,95 @@ namespace OpenNest.Geometry
|
|||||||
return double.MaxValue;
|
return double.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the distance from a point along a direction to an arc.
|
||||||
|
/// Solves ray-circle intersection, then constrains hits to the arc's
|
||||||
|
/// angular span. Returns double.MaxValue if no hit.
|
||||||
|
/// </summary>
|
||||||
|
[System.Runtime.CompilerServices.MethodImpl(
|
||||||
|
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static double RayArcDistance(
|
||||||
|
double vx, double vy,
|
||||||
|
double cx, double cy, double r,
|
||||||
|
double startAngle, double endAngle, bool reversed,
|
||||||
|
double dirX, double dirY)
|
||||||
|
{
|
||||||
|
// Ray: P = (vx,vy) + t*(dirX,dirY)
|
||||||
|
// Circle: (x-cx)^2 + (y-cy)^2 = r^2
|
||||||
|
var ox = vx - cx;
|
||||||
|
var oy = vy - cy;
|
||||||
|
|
||||||
|
// a = dirX^2 + dirY^2 = 1 for unit direction, but handle general case
|
||||||
|
var a = dirX * dirX + dirY * dirY;
|
||||||
|
var b = 2.0 * (ox * dirX + oy * dirY);
|
||||||
|
var c = ox * ox + oy * oy - r * r;
|
||||||
|
|
||||||
|
var discriminant = b * b - 4.0 * a * c;
|
||||||
|
if (discriminant < 0)
|
||||||
|
return double.MaxValue;
|
||||||
|
|
||||||
|
var sqrtD = System.Math.Sqrt(discriminant);
|
||||||
|
var inv2a = 1.0 / (2.0 * a);
|
||||||
|
var t1 = (-b - sqrtD) * inv2a;
|
||||||
|
var t2 = (-b + sqrtD) * inv2a;
|
||||||
|
|
||||||
|
var best = double.MaxValue;
|
||||||
|
|
||||||
|
if (t1 > -Tolerance.Epsilon)
|
||||||
|
{
|
||||||
|
var hitAngle = Angle.NormalizeRad(System.Math.Atan2(
|
||||||
|
vy + t1 * dirY - cy, vx + t1 * dirX - cx));
|
||||||
|
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
|
||||||
|
best = t1 > Tolerance.Epsilon ? t1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t2 > -Tolerance.Epsilon && t2 < best)
|
||||||
|
{
|
||||||
|
var hitAngle = Angle.NormalizeRad(System.Math.Atan2(
|
||||||
|
vy + t2 * dirY - cy, vx + t2 * dirX - cx));
|
||||||
|
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
|
||||||
|
best = t2 > Tolerance.Epsilon ? t2 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the distance from a point along a direction to a full circle.
|
||||||
|
/// Returns double.MaxValue if no hit.
|
||||||
|
/// </summary>
|
||||||
|
[System.Runtime.CompilerServices.MethodImpl(
|
||||||
|
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static double RayCircleDistance(
|
||||||
|
double vx, double vy,
|
||||||
|
double cx, double cy, double r,
|
||||||
|
double dirX, double dirY)
|
||||||
|
{
|
||||||
|
var ox = vx - cx;
|
||||||
|
var oy = vy - cy;
|
||||||
|
|
||||||
|
var a = dirX * dirX + dirY * dirY;
|
||||||
|
var b = 2.0 * (ox * dirX + oy * dirY);
|
||||||
|
var c = ox * ox + oy * oy - r * r;
|
||||||
|
|
||||||
|
var discriminant = b * b - 4.0 * a * c;
|
||||||
|
if (discriminant < 0)
|
||||||
|
return double.MaxValue;
|
||||||
|
|
||||||
|
var sqrtD = System.Math.Sqrt(discriminant);
|
||||||
|
var t = (-b - sqrtD) / (2.0 * a);
|
||||||
|
|
||||||
|
if (t > Tolerance.Epsilon) return t;
|
||||||
|
if (t >= -Tolerance.Epsilon) return 0;
|
||||||
|
|
||||||
|
// First root is behind us, try the second
|
||||||
|
t = (-b + sqrtD) / (2.0 * a);
|
||||||
|
if (t > Tolerance.Epsilon) return t;
|
||||||
|
if (t >= -Tolerance.Epsilon) return 0;
|
||||||
|
|
||||||
|
return double.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes the minimum translation distance along a push direction before
|
/// Computes the minimum translation distance along a push direction before
|
||||||
/// any edge of movingLines contacts any edge of stationaryLines.
|
/// any edge of movingLines contacts any edge of stationaryLines.
|
||||||
|
|||||||
@@ -39,7 +39,30 @@ namespace OpenNest
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, double chordTolerance = 0.001)
|
/// <summary>
|
||||||
|
/// Returns the perimeter entities (Line, Arc, Circle) with spacing offset applied,
|
||||||
|
/// without tessellation. Much faster than GetOffsetPartLines for parts with many arcs.
|
||||||
|
/// </summary>
|
||||||
|
public static List<Entity> GetOffsetPerimeterEntities(Part part, double spacing)
|
||||||
|
{
|
||||||
|
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||||
|
var profile = new ShapeProfile(
|
||||||
|
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||||
|
|
||||||
|
var offsetShape = profile.Perimeter.OffsetOutward(spacing);
|
||||||
|
if (offsetShape == null)
|
||||||
|
return new List<Entity>();
|
||||||
|
|
||||||
|
// Offset the shape's entities to the part's location.
|
||||||
|
// OffsetOutward creates a new Shape, so mutating is safe.
|
||||||
|
foreach (var entity in offsetShape.Entities)
|
||||||
|
entity.Offset(part.Location);
|
||||||
|
|
||||||
|
return offsetShape.Entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Line> GetOffsetPartLines(Part part, double spacing, double chordTolerance = 0.001,
|
||||||
|
bool perimeterOnly = false)
|
||||||
{
|
{
|
||||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||||
var profile = new ShapeProfile(
|
var profile = new ShapeProfile(
|
||||||
@@ -50,9 +73,12 @@ namespace OpenNest
|
|||||||
AddOffsetLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
AddOffsetLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||||
chordTolerance, part.Location);
|
chordTolerance, part.Location);
|
||||||
|
|
||||||
foreach (var cutout in profile.Cutouts)
|
if (!perimeterOnly)
|
||||||
AddOffsetLines(lines, cutout.OffsetInward(totalSpacing),
|
{
|
||||||
chordTolerance, part.Location);
|
foreach (var cutout in profile.Cutouts)
|
||||||
|
AddOffsetLines(lines, cutout.OffsetInward(totalSpacing),
|
||||||
|
chordTolerance, part.Location);
|
||||||
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using OpenNest.Geometry;
|
|||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -49,6 +50,8 @@ namespace OpenNest.Engine.BestFit
|
|||||||
|
|
||||||
var allCandidates = candidateBags.SelectMany(c => c).ToList();
|
var allCandidates = candidateBags.SelectMany(c => c).ToList();
|
||||||
|
|
||||||
|
Debug.WriteLine($"[BestFitFinder] {strategies.Count} strategies, {allCandidates.Count} candidates");
|
||||||
|
|
||||||
var results = _evaluator.EvaluateAll(allCandidates);
|
var results = _evaluator.EvaluateAll(allCandidates);
|
||||||
|
|
||||||
_filter.Apply(results);
|
_filter.Apply(results);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
using OpenNest.Math;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -17,7 +18,6 @@ namespace OpenNest.Engine.BestFit
|
|||||||
var allMovingVerts = ExtractUniqueVertices(movingTemplateLines);
|
var allMovingVerts = ExtractUniqueVertices(movingTemplateLines);
|
||||||
var allStationaryVerts = ExtractUniqueVertices(stationaryLines);
|
var allStationaryVerts = ExtractUniqueVertices(stationaryLines);
|
||||||
|
|
||||||
// Pre-filter vertices per unique direction (typically 4 cardinal directions).
|
|
||||||
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||||
|
|
||||||
foreach (var offset in offsets)
|
foreach (var offset in offsets)
|
||||||
@@ -43,7 +43,6 @@ namespace OpenNest.Engine.BestFit
|
|||||||
|
|
||||||
var minDist = double.MaxValue;
|
var minDist = double.MaxValue;
|
||||||
|
|
||||||
// Case 1: Leading moving vertices → stationary edges
|
|
||||||
for (var v = 0; v < leadingMoving.Length; v++)
|
for (var v = 0; v < leadingMoving.Length; v++)
|
||||||
{
|
{
|
||||||
var vx = leadingMoving[v].X + offset.Dx;
|
var vx = leadingMoving[v].X + offset.Dx;
|
||||||
@@ -66,7 +65,6 @@ namespace OpenNest.Engine.BestFit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Facing stationary vertices → moving edges (opposite direction)
|
|
||||||
for (var v = 0; v < facingStationary.Length; v++)
|
for (var v = 0; v < facingStationary.Length; v++)
|
||||||
{
|
{
|
||||||
var svx = facingStationary[v].X;
|
var svx = facingStationary[v].X;
|
||||||
@@ -95,6 +93,178 @@ namespace OpenNest.Engine.BestFit
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double[] ComputeDistances(
|
||||||
|
List<Entity> stationaryEntities,
|
||||||
|
List<Entity> movingEntities,
|
||||||
|
SlideOffset[] offsets)
|
||||||
|
{
|
||||||
|
var count = offsets.Length;
|
||||||
|
var results = new double[count];
|
||||||
|
|
||||||
|
var allMovingVerts = ExtractVerticesFromEntities(movingEntities);
|
||||||
|
var allStationaryVerts = ExtractVerticesFromEntities(stationaryEntities);
|
||||||
|
|
||||||
|
var vertexCache = new Dictionary<(double, double), (Vector[] leading, Vector[] facing)>();
|
||||||
|
|
||||||
|
foreach (var offset in offsets)
|
||||||
|
{
|
||||||
|
var key = (offset.DirX, offset.DirY);
|
||||||
|
if (vertexCache.ContainsKey(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var leading = FilterVerticesByProjection(allMovingVerts, offset.DirX, offset.DirY, keepHigh: true);
|
||||||
|
var facing = FilterVerticesByProjection(allStationaryVerts, offset.DirX, offset.DirY, keepHigh: false);
|
||||||
|
vertexCache[key] = (leading, facing);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Threading.Tasks.Parallel.For(0, count, i =>
|
||||||
|
{
|
||||||
|
var offset = offsets[i];
|
||||||
|
var dirX = offset.DirX;
|
||||||
|
var dirY = offset.DirY;
|
||||||
|
var oppX = -dirX;
|
||||||
|
var oppY = -dirY;
|
||||||
|
|
||||||
|
var (leadingMoving, facingStationary) = vertexCache[(dirX, dirY)];
|
||||||
|
|
||||||
|
var minDist = double.MaxValue;
|
||||||
|
|
||||||
|
// Case 1: Leading moving vertices → stationary entities
|
||||||
|
for (var v = 0; v < leadingMoving.Length; v++)
|
||||||
|
{
|
||||||
|
var vx = leadingMoving[v].X + offset.Dx;
|
||||||
|
var vy = leadingMoving[v].Y + offset.Dy;
|
||||||
|
|
||||||
|
for (var j = 0; j < stationaryEntities.Count; j++)
|
||||||
|
{
|
||||||
|
var d = RayEntityDistance(vx, vy, stationaryEntities[j], 0, 0, dirX, dirY);
|
||||||
|
|
||||||
|
if (d < minDist)
|
||||||
|
{
|
||||||
|
minDist = d;
|
||||||
|
if (d <= 0) { results[i] = 0; return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Facing stationary vertices → moving entities (opposite direction)
|
||||||
|
for (var v = 0; v < facingStationary.Length; v++)
|
||||||
|
{
|
||||||
|
var svx = facingStationary[v].X;
|
||||||
|
var svy = facingStationary[v].Y;
|
||||||
|
|
||||||
|
for (var j = 0; j < movingEntities.Count; j++)
|
||||||
|
{
|
||||||
|
var d = RayEntityDistance(svx, svy, movingEntities[j], offset.Dx, offset.Dy, oppX, oppY);
|
||||||
|
|
||||||
|
if (d < minDist)
|
||||||
|
{
|
||||||
|
minDist = d;
|
||||||
|
if (d <= 0) { results[i] = 0; return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results[i] = minDist;
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double RayEntityDistance(
|
||||||
|
double vx, double vy, Entity entity,
|
||||||
|
double entityOffsetX, double entityOffsetY,
|
||||||
|
double dirX, double dirY)
|
||||||
|
{
|
||||||
|
if (entity is Line line)
|
||||||
|
{
|
||||||
|
return SpatialQuery.RayEdgeDistance(
|
||||||
|
vx, vy,
|
||||||
|
line.StartPoint.X + entityOffsetX, line.StartPoint.Y + entityOffsetY,
|
||||||
|
line.EndPoint.X + entityOffsetX, line.EndPoint.Y + entityOffsetY,
|
||||||
|
dirX, dirY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity is Arc arc)
|
||||||
|
{
|
||||||
|
return SpatialQuery.RayArcDistance(
|
||||||
|
vx, vy,
|
||||||
|
arc.Center.X + entityOffsetX, arc.Center.Y + entityOffsetY,
|
||||||
|
arc.Radius,
|
||||||
|
arc.StartAngle, arc.EndAngle, arc.IsReversed,
|
||||||
|
dirX, dirY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity is Circle circle)
|
||||||
|
{
|
||||||
|
return SpatialQuery.RayCircleDistance(
|
||||||
|
vx, vy,
|
||||||
|
circle.Center.X + entityOffsetX, circle.Center.Y + entityOffsetY,
|
||||||
|
circle.Radius,
|
||||||
|
dirX, dirY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return double.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector[] ExtractVerticesFromEntities(List<Entity> entities)
|
||||||
|
{
|
||||||
|
var vertices = new HashSet<Vector>();
|
||||||
|
|
||||||
|
for (var i = 0; i < entities.Count; i++)
|
||||||
|
{
|
||||||
|
var entity = entities[i];
|
||||||
|
|
||||||
|
if (entity is Line line)
|
||||||
|
{
|
||||||
|
vertices.Add(line.StartPoint);
|
||||||
|
vertices.Add(line.EndPoint);
|
||||||
|
}
|
||||||
|
else if (entity is Arc arc)
|
||||||
|
{
|
||||||
|
vertices.Add(arc.StartPoint());
|
||||||
|
vertices.Add(arc.EndPoint());
|
||||||
|
AddArcExtremes(vertices, arc);
|
||||||
|
}
|
||||||
|
else if (entity is Circle circle)
|
||||||
|
{
|
||||||
|
// Four cardinal points
|
||||||
|
vertices.Add(new Vector(circle.Center.X + circle.Radius, circle.Center.Y));
|
||||||
|
vertices.Add(new Vector(circle.Center.X - circle.Radius, circle.Center.Y));
|
||||||
|
vertices.Add(new Vector(circle.Center.X, circle.Center.Y + circle.Radius));
|
||||||
|
vertices.Add(new Vector(circle.Center.X, circle.Center.Y - circle.Radius));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vertices.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddArcExtremes(HashSet<Vector> points, Arc arc)
|
||||||
|
{
|
||||||
|
var a1 = arc.StartAngle;
|
||||||
|
var a2 = arc.EndAngle;
|
||||||
|
var reversed = arc.IsReversed;
|
||||||
|
|
||||||
|
if (reversed)
|
||||||
|
Generic.Swap(ref a1, ref a2);
|
||||||
|
|
||||||
|
// Right (0°)
|
||||||
|
if (Angle.IsBetweenRad(Angle.TwoPI, a1, a2))
|
||||||
|
points.Add(new Vector(arc.Center.X + arc.Radius, arc.Center.Y));
|
||||||
|
|
||||||
|
// Top (90°)
|
||||||
|
if (Angle.IsBetweenRad(Angle.HalfPI, a1, a2))
|
||||||
|
points.Add(new Vector(arc.Center.X, arc.Center.Y + arc.Radius));
|
||||||
|
|
||||||
|
// Left (180°)
|
||||||
|
if (Angle.IsBetweenRad(System.Math.PI, a1, a2))
|
||||||
|
points.Add(new Vector(arc.Center.X - arc.Radius, arc.Center.Y));
|
||||||
|
|
||||||
|
// Bottom (270°)
|
||||||
|
if (Angle.IsBetweenRad(System.Math.PI * 1.5, a1, a2))
|
||||||
|
points.Add(new Vector(arc.Center.X, arc.Center.Y - arc.Radius));
|
||||||
|
}
|
||||||
|
|
||||||
private static Vector[] ExtractUniqueVertices(List<Line> lines)
|
private static Vector[] ExtractUniqueVertices(List<Line> lines)
|
||||||
{
|
{
|
||||||
var vertices = new HashSet<Vector>();
|
var vertices = new HashSet<Vector>();
|
||||||
@@ -106,11 +276,6 @@ namespace OpenNest.Engine.BestFit
|
|||||||
return vertices.ToArray();
|
return vertices.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Filters vertices by their projection onto the push direction.
|
|
||||||
/// keepHigh=true returns the leading half (front face, closest to target).
|
|
||||||
/// keepHigh=false returns the facing half (side facing the approaching part).
|
|
||||||
/// </summary>
|
|
||||||
private static Vector[] FilterVerticesByProjection(
|
private static Vector[] FilterVerticesByProjection(
|
||||||
Vector[] vertices, double dirX, double dirY, bool keepHigh)
|
Vector[] vertices, double dirX, double dirY, bool keepHigh)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ namespace OpenNest.Engine.BestFit
|
|||||||
flatOffsets, count, directions);
|
flatOffsets, count, directions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double[] ComputeDistances(
|
||||||
|
List<Entity> stationaryEntities,
|
||||||
|
List<Entity> movingEntities,
|
||||||
|
SlideOffset[] offsets)
|
||||||
|
{
|
||||||
|
// GPU path doesn't support native entities yet — fall back to CPU.
|
||||||
|
var cpu = new CpuDistanceComputer();
|
||||||
|
return cpu.ComputeDistances(stationaryEntities, movingEntities, offsets);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a unit direction vector to a PushDirection int for the GPU interface.
|
/// Maps a unit direction vector to a PushDirection int for the GPU interface.
|
||||||
/// Left=0, Down=1, Right=2, Up=3.
|
/// Left=0, Down=1, Right=2, Up=3.
|
||||||
|
|||||||
@@ -9,5 +9,10 @@ namespace OpenNest.Engine.BestFit
|
|||||||
List<Line> stationaryLines,
|
List<Line> stationaryLines,
|
||||||
List<Line> movingTemplateLines,
|
List<Line> movingTemplateLines,
|
||||||
SlideOffset[] offsets);
|
SlideOffset[] offsets);
|
||||||
|
|
||||||
|
double[] ComputeDistances(
|
||||||
|
List<Entity> stationaryEntities,
|
||||||
|
List<Entity> movingEntities,
|
||||||
|
SlideOffset[] offsets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ namespace OpenNest.Engine.BestFit
|
|||||||
var part2Template = Part.CreateAtOrigin(drawing, Part2Rotation);
|
var part2Template = Part.CreateAtOrigin(drawing, Part2Rotation);
|
||||||
|
|
||||||
var halfSpacing = spacing / 2;
|
var halfSpacing = spacing / 2;
|
||||||
var part1Lines = PartGeometry.GetOffsetPartLines(part1, halfSpacing);
|
var part1Entities = PartGeometry.GetOffsetPerimeterEntities(part1, halfSpacing);
|
||||||
var part2TemplateLines = PartGeometry.GetOffsetPartLines(part2Template, halfSpacing);
|
var part2Entities = PartGeometry.GetOffsetPerimeterEntities(part2Template, halfSpacing);
|
||||||
|
|
||||||
var bbox1 = part1.BoundingBox;
|
var bbox1 = part1.BoundingBox;
|
||||||
var bbox2 = part2Template.BoundingBox;
|
var bbox2 = part2Template.BoundingBox;
|
||||||
@@ -48,7 +48,7 @@ namespace OpenNest.Engine.BestFit
|
|||||||
return candidates;
|
return candidates;
|
||||||
|
|
||||||
var distances = _distanceComputer.ComputeDistances(
|
var distances = _distanceComputer.ComputeDistances(
|
||||||
part1Lines, part2TemplateLines, offsets);
|
part1Entities, part2Entities, offsets);
|
||||||
|
|
||||||
var testNumber = 0;
|
var testNumber = 0;
|
||||||
|
|
||||||
@@ -90,15 +90,18 @@ namespace OpenNest.Engine.BestFit
|
|||||||
if (isHorizontalPush)
|
if (isHorizontalPush)
|
||||||
{
|
{
|
||||||
// Perpendicular sweep along Y → Width; push extent along X → Length
|
// Perpendicular sweep along Y → Width; push extent along X → Length
|
||||||
perpMin = -(bbox2.Width + spacing);
|
// Trim to offsets where the parts overlap by at least 50%.
|
||||||
perpMax = bbox1.Width + bbox2.Width + spacing;
|
var halfOverlap = bbox2.Width * 0.5;
|
||||||
|
perpMin = -(halfOverlap - spacing);
|
||||||
|
perpMax = bbox1.Width + halfOverlap + spacing;
|
||||||
pushStartOffset = bbox1.Length + bbox2.Length + spacing * 2;
|
pushStartOffset = bbox1.Length + bbox2.Length + spacing * 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Perpendicular sweep along X → Length; push extent along Y → Width
|
// Perpendicular sweep along X → Length; push extent along Y → Width
|
||||||
perpMin = -(bbox2.Length + spacing);
|
var halfOverlap = bbox2.Length * 0.5;
|
||||||
perpMax = bbox1.Length + bbox2.Length + spacing;
|
perpMin = -(halfOverlap - spacing);
|
||||||
|
perpMax = bbox1.Length + halfOverlap + spacing;
|
||||||
pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2;
|
pushStartOffset = bbox1.Width + bbox2.Width + spacing * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,24 +139,42 @@ namespace OpenNest
|
|||||||
var bestFits = BestFitCache.GetOrCompute(
|
var bestFits = BestFitCache.GetOrCompute(
|
||||||
drawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing);
|
drawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing);
|
||||||
|
|
||||||
var best = SelectBestFitPair(bestFits);
|
List<Part> bestPlacement = null;
|
||||||
if (best == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// BuildParts produces landscape orientation (Width >= Height).
|
foreach (var fit in bestFits)
|
||||||
// Try both landscape and portrait (90° rotated) and let the
|
{
|
||||||
// engine's comparer pick the better orientation.
|
if (!fit.Keep)
|
||||||
var landscape = best.BuildParts(drawing);
|
continue;
|
||||||
var portrait = RotatePair90(landscape);
|
|
||||||
|
|
||||||
var lFits = TryOffsetToWorkArea(landscape, workArea);
|
// Skip pairs that can't possibly fit the work area in either orientation.
|
||||||
var pFits = TryOffsetToWorkArea(portrait, workArea);
|
if (fit.ShortestSide > System.Math.Min(workArea.Width, workArea.Length) + Tolerance.Epsilon)
|
||||||
|
continue;
|
||||||
|
if (fit.LongestSide > System.Math.Max(workArea.Width, workArea.Length) + Tolerance.Epsilon)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!lFits && !pFits)
|
var landscape = fit.BuildParts(drawing);
|
||||||
return null;
|
var portrait = RotatePair90(landscape);
|
||||||
if (lFits && pFits)
|
|
||||||
return IsBetterFill(portrait, landscape, workArea) ? portrait : landscape;
|
var lFits = TryOffsetToWorkArea(landscape, workArea);
|
||||||
return lFits ? landscape : portrait;
|
var pFits = TryOffsetToWorkArea(portrait, workArea);
|
||||||
|
|
||||||
|
// Pick the better orientation for this pair.
|
||||||
|
List<Part> candidate = null;
|
||||||
|
if (lFits && pFits)
|
||||||
|
candidate = IsBetterFill(portrait, landscape, workArea) ? portrait : landscape;
|
||||||
|
else if (lFits)
|
||||||
|
candidate = landscape;
|
||||||
|
else if (pFits)
|
||||||
|
candidate = portrait;
|
||||||
|
|
||||||
|
if (candidate == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (bestPlacement == null || IsBetterFill(candidate, bestPlacement, workArea))
|
||||||
|
bestPlacement = candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestPlacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Part> RotatePair90(List<Part> parts)
|
private static List<Part> RotatePair90(List<Part> parts)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenNest.Engine;
|
using OpenNest.Engine;
|
||||||
using OpenNest.Engine.BestFit;
|
|
||||||
using OpenNest.Engine.Fill;
|
using OpenNest.Engine.Fill;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
@@ -27,20 +26,6 @@ namespace OpenNest
|
|||||||
|
|
||||||
public override ShrinkAxis TrimAxis => ShrinkAxis.Length;
|
public override ShrinkAxis TrimAxis => ShrinkAxis.Length;
|
||||||
|
|
||||||
protected override BestFitResult SelectBestFitPair(List<BestFitResult> results)
|
|
||||||
{
|
|
||||||
BestFitResult best = null;
|
|
||||||
|
|
||||||
foreach (var r in results)
|
|
||||||
{
|
|
||||||
if (!r.Keep) continue;
|
|
||||||
if (best == null || r.BoundingHeight < best.BoundingHeight)
|
|
||||||
best = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override List<double> BuildAngles(NestItem item, ClassificationResult classification, Box workArea)
|
public override List<double> BuildAngles(NestItem item, ClassificationResult classification, Box workArea)
|
||||||
{
|
{
|
||||||
var baseAngles = new List<double> { classification.PrimaryAngle, classification.PrimaryAngle + Angle.HalfPI };
|
var baseAngles = new List<double> { classification.PrimaryAngle, classification.PrimaryAngle + Angle.HalfPI };
|
||||||
|
|||||||
@@ -56,11 +56,6 @@ namespace OpenNest
|
|||||||
|
|
||||||
protected FillPolicy BuildPolicy() => new FillPolicy(Comparer, PreferredDirection);
|
protected FillPolicy BuildPolicy() => new FillPolicy(Comparer, PreferredDirection);
|
||||||
|
|
||||||
protected virtual BestFitResult SelectBestFitPair(List<BestFitResult> results)
|
|
||||||
{
|
|
||||||
return results.FirstOrDefault(r => r.Keep);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Virtual methods (side-effect-free, return parts) ---
|
// --- Virtual methods (side-effect-free, return parts) ---
|
||||||
|
|
||||||
public virtual List<Part> Fill(NestItem item, Box workArea,
|
public virtual List<Part> Fill(NestItem item, Box workArea,
|
||||||
@@ -338,45 +333,56 @@ namespace OpenNest
|
|||||||
|
|
||||||
var bestFits = BestFitCache.GetOrCompute(
|
var bestFits = BestFitCache.GetOrCompute(
|
||||||
item.Drawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing);
|
item.Drawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing);
|
||||||
var bestFit = SelectBestFitPair(bestFits);
|
|
||||||
if (bestFit == null) continue;
|
|
||||||
|
|
||||||
var parts = bestFit.BuildParts(item.Drawing);
|
List<Part> bestPlacement = null;
|
||||||
var pairBbox = ((IEnumerable<IBoundable>)parts).GetBoundingBox();
|
Box bestTarget = null;
|
||||||
var pairW = pairBbox.Width;
|
|
||||||
var pairL = pairBbox.Length;
|
|
||||||
var minDim = System.Math.Min(pairW, pairL);
|
|
||||||
|
|
||||||
var remnants = finder.FindRemnants(minDim);
|
foreach (var fit in bestFits)
|
||||||
Box target = null;
|
|
||||||
|
|
||||||
foreach (var r in remnants)
|
|
||||||
{
|
{
|
||||||
if (pairW <= r.Width + Tolerance.Epsilon &&
|
if (!fit.Keep)
|
||||||
pairL <= r.Length + Tolerance.Epsilon)
|
continue;
|
||||||
|
|
||||||
|
var parts = fit.BuildParts(item.Drawing);
|
||||||
|
var pairBbox = ((IEnumerable<IBoundable>)parts).GetBoundingBox();
|
||||||
|
var pairW = pairBbox.Width;
|
||||||
|
var pairL = pairBbox.Length;
|
||||||
|
var minDim = System.Math.Min(pairW, pairL);
|
||||||
|
|
||||||
|
var remnants = finder.FindRemnants(minDim);
|
||||||
|
|
||||||
|
foreach (var r in remnants)
|
||||||
{
|
{
|
||||||
target = r;
|
if (pairW <= r.Width + Tolerance.Epsilon &&
|
||||||
break;
|
pairL <= r.Length + Tolerance.Epsilon)
|
||||||
|
{
|
||||||
|
var offset = r.Location - pairBbox.Location;
|
||||||
|
foreach (var p in parts)
|
||||||
|
{
|
||||||
|
p.Offset(offset);
|
||||||
|
p.UpdateBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestPlacement == null || IsBetterFill(parts, bestPlacement, r))
|
||||||
|
{
|
||||||
|
bestPlacement = parts;
|
||||||
|
bestTarget = r;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target == null) continue;
|
if (bestPlacement == null) continue;
|
||||||
|
|
||||||
var offset = target.Location - pairBbox.Location;
|
result.AddRange(bestPlacement);
|
||||||
foreach (var p in parts)
|
|
||||||
{
|
|
||||||
p.Offset(offset);
|
|
||||||
p.UpdateBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.AddRange(parts);
|
|
||||||
item.Quantity = 0;
|
item.Quantity = 0;
|
||||||
|
|
||||||
var envelope = ((IEnumerable<IBoundable>)parts).GetBoundingBox();
|
var envelope = ((IEnumerable<IBoundable>)bestPlacement).GetBoundingBox();
|
||||||
finder.AddObstacle(envelope.Offset(Plate.PartSpacing));
|
finder.AddObstacle(envelope.Offset(Plate.PartSpacing));
|
||||||
|
|
||||||
Debug.WriteLine($"[Nest] Placed best-fit pair for {item.Drawing.Name} " +
|
Debug.WriteLine($"[Nest] Placed best-fit pair for {item.Drawing.Name} " +
|
||||||
$"at ({target.X:F1},{target.Y:F1}), size {pairW:F1}x{pairL:F1}");
|
$"at ({bestTarget.X:F1},{bestTarget.Y:F1}), " +
|
||||||
|
$"size {envelope.Width:F1}x{envelope.Length:F1}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using OpenNest.Engine;
|
using OpenNest.Engine;
|
||||||
|
using OpenNest.Engine.BestFit;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
using System;
|
using System;
|
||||||
@@ -22,7 +23,8 @@ namespace OpenNest
|
|||||||
if (items == null || items.Count == 0 || plateOptions == null || plateOptions.Count == 0)
|
if (items == null || items.Count == 0 || plateOptions == null || plateOptions.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Find the minimum dimension needed to fit the largest part.
|
// Find the minimum dimension needed to fit the largest part,
|
||||||
|
// skipping items that are too large for every plate option.
|
||||||
var minPartWidth = 0.0;
|
var minPartWidth = 0.0;
|
||||||
var minPartLength = 0.0;
|
var minPartLength = 0.0;
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
@@ -31,6 +33,14 @@ namespace OpenNest
|
|||||||
var bb = item.Drawing.Program.BoundingBox();
|
var bb = item.Drawing.Program.BoundingBox();
|
||||||
var shortSide = System.Math.Min(bb.Width, bb.Length);
|
var shortSide = System.Math.Min(bb.Width, bb.Length);
|
||||||
var longSide = System.Math.Max(bb.Width, bb.Length);
|
var longSide = System.Math.Max(bb.Width, bb.Length);
|
||||||
|
|
||||||
|
if (!plateOptions.Any(o => FitsPart(o, shortSide, longSide, templatePlate.EdgeSpacing)))
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"[PlateOptimizer] Skipping oversized item '{item.Drawing.Name}' " +
|
||||||
|
$"({shortSide:F1}x{longSide:F1}) — does not fit any plate option");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (shortSide > minPartWidth) minPartWidth = shortSide;
|
if (shortSide > minPartWidth) minPartWidth = shortSide;
|
||||||
if (longSide > minPartLength) minPartLength = longSide;
|
if (longSide > minPartLength) minPartLength = longSide;
|
||||||
}
|
}
|
||||||
@@ -44,6 +54,19 @@ namespace OpenNest
|
|||||||
if (candidates.Count == 0)
|
if (candidates.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
// Pre-compute best fits for all candidate plate sizes at once.
|
||||||
|
// This runs the expensive GPU evaluation once on the largest plate
|
||||||
|
// and filters the results for each smaller size.
|
||||||
|
var plateSizes = candidates
|
||||||
|
.Select(o => (Width: o.Length, Height: o.Width))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.Quantity <= 0) continue;
|
||||||
|
BestFitCache.ComputeForSizes(item.Drawing, templatePlate.PartSpacing, plateSizes);
|
||||||
|
}
|
||||||
|
|
||||||
PlateOptimizerResult best = null;
|
PlateOptimizerResult best = null;
|
||||||
|
|
||||||
foreach (var option in candidates)
|
foreach (var option in candidates)
|
||||||
@@ -58,9 +81,10 @@ namespace OpenNest
|
|||||||
if (IsBetter(result, best))
|
if (IsBetter(result, best))
|
||||||
best = result;
|
best = result;
|
||||||
|
|
||||||
// Early exit: when salvage is zero, cheapest plate that fits everything wins.
|
// Early exit: when all items fit, larger plates can only have
|
||||||
// With salvage > 0, larger plates may have lower net cost, so keep searching.
|
// worse utilization and higher cost. With salvage < 100%, the
|
||||||
if (salvageRate <= 0)
|
// remnant credit never offsets the extra plate cost, so skip.
|
||||||
|
if (salvageRate < 1.0)
|
||||||
{
|
{
|
||||||
var allPlaced = items.All(i => i.Quantity <= 0 ||
|
var allPlaced = items.All(i => i.Quantity <= 0 ||
|
||||||
result.Parts.Count(p => p.BaseDrawing.Name == i.Drawing.Name) >= i.Quantity);
|
result.Parts.Count(p => p.BaseDrawing.Name == i.Drawing.Name) >= i.Quantity);
|
||||||
@@ -158,8 +182,8 @@ namespace OpenNest
|
|||||||
if (!candidate.NetCost.IsEqualTo(current.NetCost))
|
if (!candidate.NetCost.IsEqualTo(current.NetCost))
|
||||||
return candidate.NetCost < current.NetCost;
|
return candidate.NetCost < current.NetCost;
|
||||||
|
|
||||||
// 3. Smaller plate area as tiebreak.
|
// 3. Higher utilization (tighter density) as tiebreak.
|
||||||
return candidate.ChosenSize.Area < current.ChosenSize.Area;
|
return candidate.Utilization > current.Utilization;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenNest.Engine;
|
using OpenNest.Engine;
|
||||||
using OpenNest.Engine.BestFit;
|
|
||||||
using OpenNest.Engine.Fill;
|
using OpenNest.Engine.Fill;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
@@ -25,20 +24,6 @@ namespace OpenNest
|
|||||||
|
|
||||||
public override NestDirection? PreferredDirection => NestDirection.Horizontal;
|
public override NestDirection? PreferredDirection => NestDirection.Horizontal;
|
||||||
|
|
||||||
protected override BestFitResult SelectBestFitPair(List<BestFitResult> results)
|
|
||||||
{
|
|
||||||
BestFitResult best = null;
|
|
||||||
|
|
||||||
foreach (var r in results)
|
|
||||||
{
|
|
||||||
if (!r.Keep) continue;
|
|
||||||
if (best == null || r.BoundingHeight < best.BoundingHeight)
|
|
||||||
best = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override List<double> BuildAngles(NestItem item, ClassificationResult classification, Box workArea)
|
public override List<double> BuildAngles(NestItem item, ClassificationResult classification, Box workArea)
|
||||||
{
|
{
|
||||||
var baseAngles = new List<double> { classification.PrimaryAngle, classification.PrimaryAngle + Angle.HalfPI };
|
var baseAngles = new List<double> { classification.PrimaryAngle, classification.PrimaryAngle + Angle.HalfPI };
|
||||||
|
|||||||
Generated
+42
-42
@@ -50,9 +50,9 @@ namespace OpenNest.Forms
|
|||||||
((System.ComponentModel.ISupportInitialize)dgvGroups).BeginInit();
|
((System.ComponentModel.ISupportInitialize)dgvGroups).BeginInit();
|
||||||
pnlBottom.SuspendLayout();
|
pnlBottom.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// grpInput
|
// grpInput
|
||||||
//
|
//
|
||||||
grpInput.Controls.Add(tbl);
|
grpInput.Controls.Add(tbl);
|
||||||
grpInput.Dock = System.Windows.Forms.DockStyle.Top;
|
grpInput.Dock = System.Windows.Forms.DockStyle.Top;
|
||||||
grpInput.Location = new System.Drawing.Point(0, 0);
|
grpInput.Location = new System.Drawing.Point(0, 0);
|
||||||
@@ -62,9 +62,9 @@ namespace OpenNest.Forms
|
|||||||
grpInput.TabIndex = 0;
|
grpInput.TabIndex = 0;
|
||||||
grpInput.TabStop = false;
|
grpInput.TabStop = false;
|
||||||
grpInput.Text = "Input";
|
grpInput.Text = "Input";
|
||||||
//
|
//
|
||||||
// tbl
|
// tbl
|
||||||
//
|
//
|
||||||
tbl.ColumnCount = 3;
|
tbl.ColumnCount = 3;
|
||||||
tbl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
tbl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||||
tbl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
tbl.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||||
@@ -92,9 +92,9 @@ namespace OpenNest.Forms
|
|||||||
tbl.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
tbl.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||||
tbl.Size = new System.Drawing.Size(792, 172);
|
tbl.Size = new System.Drawing.Size(792, 172);
|
||||||
tbl.TabIndex = 0;
|
tbl.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// lblJobName
|
// lblJobName
|
||||||
//
|
//
|
||||||
lblJobName.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
lblJobName.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||||
lblJobName.AutoSize = true;
|
lblJobName.AutoSize = true;
|
||||||
lblJobName.Location = new System.Drawing.Point(6, 13);
|
lblJobName.Location = new System.Drawing.Point(6, 13);
|
||||||
@@ -103,9 +103,9 @@ namespace OpenNest.Forms
|
|||||||
lblJobName.Size = new System.Drawing.Size(63, 15);
|
lblJobName.Size = new System.Drawing.Size(63, 15);
|
||||||
lblJobName.TabIndex = 0;
|
lblJobName.TabIndex = 0;
|
||||||
lblJobName.Text = "Job Name:";
|
lblJobName.Text = "Job Name:";
|
||||||
//
|
//
|
||||||
// txtJobName
|
// txtJobName
|
||||||
//
|
//
|
||||||
tbl.SetColumnSpan(txtJobName, 2);
|
tbl.SetColumnSpan(txtJobName, 2);
|
||||||
txtJobName.Dock = System.Windows.Forms.DockStyle.Fill;
|
txtJobName.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
txtJobName.Location = new System.Drawing.Point(79, 9);
|
txtJobName.Location = new System.Drawing.Point(79, 9);
|
||||||
@@ -113,9 +113,9 @@ namespace OpenNest.Forms
|
|||||||
txtJobName.Name = "txtJobName";
|
txtJobName.Name = "txtJobName";
|
||||||
txtJobName.Size = new System.Drawing.Size(707, 23);
|
txtJobName.Size = new System.Drawing.Size(707, 23);
|
||||||
txtJobName.TabIndex = 1;
|
txtJobName.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// lblBomFile
|
// lblBomFile
|
||||||
//
|
//
|
||||||
lblBomFile.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
lblBomFile.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||||
lblBomFile.AutoSize = true;
|
lblBomFile.AutoSize = true;
|
||||||
lblBomFile.Location = new System.Drawing.Point(6, 45);
|
lblBomFile.Location = new System.Drawing.Point(6, 45);
|
||||||
@@ -124,9 +124,9 @@ namespace OpenNest.Forms
|
|||||||
lblBomFile.Size = new System.Drawing.Size(58, 15);
|
lblBomFile.Size = new System.Drawing.Size(58, 15);
|
||||||
lblBomFile.TabIndex = 2;
|
lblBomFile.TabIndex = 2;
|
||||||
lblBomFile.Text = "BOM File:";
|
lblBomFile.Text = "BOM File:";
|
||||||
//
|
//
|
||||||
// txtBomFile
|
// txtBomFile
|
||||||
//
|
//
|
||||||
txtBomFile.Dock = System.Windows.Forms.DockStyle.Fill;
|
txtBomFile.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
txtBomFile.Location = new System.Drawing.Point(79, 41);
|
txtBomFile.Location = new System.Drawing.Point(79, 41);
|
||||||
txtBomFile.Margin = new System.Windows.Forms.Padding(3, 6, 3, 3);
|
txtBomFile.Margin = new System.Windows.Forms.Padding(3, 6, 3, 3);
|
||||||
@@ -134,9 +134,9 @@ namespace OpenNest.Forms
|
|||||||
txtBomFile.ReadOnly = true;
|
txtBomFile.ReadOnly = true;
|
||||||
txtBomFile.Size = new System.Drawing.Size(669, 23);
|
txtBomFile.Size = new System.Drawing.Size(669, 23);
|
||||||
txtBomFile.TabIndex = 3;
|
txtBomFile.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// btnBrowseBom
|
// btnBrowseBom
|
||||||
//
|
//
|
||||||
btnBrowseBom.Location = new System.Drawing.Point(751, 40);
|
btnBrowseBom.Location = new System.Drawing.Point(751, 40);
|
||||||
btnBrowseBom.Margin = new System.Windows.Forms.Padding(0, 5, 3, 3);
|
btnBrowseBom.Margin = new System.Windows.Forms.Padding(0, 5, 3, 3);
|
||||||
btnBrowseBom.Name = "btnBrowseBom";
|
btnBrowseBom.Name = "btnBrowseBom";
|
||||||
@@ -144,9 +144,9 @@ namespace OpenNest.Forms
|
|||||||
btnBrowseBom.TabIndex = 4;
|
btnBrowseBom.TabIndex = 4;
|
||||||
btnBrowseBom.Text = "...";
|
btnBrowseBom.Text = "...";
|
||||||
btnBrowseBom.Click += BrowseBom_Click;
|
btnBrowseBom.Click += BrowseBom_Click;
|
||||||
//
|
//
|
||||||
// lblDxfFolder
|
// lblDxfFolder
|
||||||
//
|
//
|
||||||
lblDxfFolder.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
lblDxfFolder.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||||
lblDxfFolder.AutoSize = true;
|
lblDxfFolder.AutoSize = true;
|
||||||
lblDxfFolder.Location = new System.Drawing.Point(6, 78);
|
lblDxfFolder.Location = new System.Drawing.Point(6, 78);
|
||||||
@@ -155,9 +155,9 @@ namespace OpenNest.Forms
|
|||||||
lblDxfFolder.Size = new System.Drawing.Size(67, 15);
|
lblDxfFolder.Size = new System.Drawing.Size(67, 15);
|
||||||
lblDxfFolder.TabIndex = 5;
|
lblDxfFolder.TabIndex = 5;
|
||||||
lblDxfFolder.Text = "DXF Folder:";
|
lblDxfFolder.Text = "DXF Folder:";
|
||||||
//
|
//
|
||||||
// txtDxfFolder
|
// txtDxfFolder
|
||||||
//
|
//
|
||||||
txtDxfFolder.Dock = System.Windows.Forms.DockStyle.Fill;
|
txtDxfFolder.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
txtDxfFolder.Location = new System.Drawing.Point(79, 74);
|
txtDxfFolder.Location = new System.Drawing.Point(79, 74);
|
||||||
txtDxfFolder.Margin = new System.Windows.Forms.Padding(3, 6, 3, 3);
|
txtDxfFolder.Margin = new System.Windows.Forms.Padding(3, 6, 3, 3);
|
||||||
@@ -165,9 +165,9 @@ namespace OpenNest.Forms
|
|||||||
txtDxfFolder.ReadOnly = true;
|
txtDxfFolder.ReadOnly = true;
|
||||||
txtDxfFolder.Size = new System.Drawing.Size(669, 23);
|
txtDxfFolder.Size = new System.Drawing.Size(669, 23);
|
||||||
txtDxfFolder.TabIndex = 6;
|
txtDxfFolder.TabIndex = 6;
|
||||||
//
|
//
|
||||||
// btnBrowseDxf
|
// btnBrowseDxf
|
||||||
//
|
//
|
||||||
btnBrowseDxf.Location = new System.Drawing.Point(751, 73);
|
btnBrowseDxf.Location = new System.Drawing.Point(751, 73);
|
||||||
btnBrowseDxf.Margin = new System.Windows.Forms.Padding(0, 5, 3, 3);
|
btnBrowseDxf.Margin = new System.Windows.Forms.Padding(0, 5, 3, 3);
|
||||||
btnBrowseDxf.Name = "btnBrowseDxf";
|
btnBrowseDxf.Name = "btnBrowseDxf";
|
||||||
@@ -175,9 +175,9 @@ namespace OpenNest.Forms
|
|||||||
btnBrowseDxf.TabIndex = 7;
|
btnBrowseDxf.TabIndex = 7;
|
||||||
btnBrowseDxf.Text = "...";
|
btnBrowseDxf.Text = "...";
|
||||||
btnBrowseDxf.Click += BrowseDxf_Click;
|
btnBrowseDxf.Click += BrowseDxf_Click;
|
||||||
//
|
//
|
||||||
// lblPlateSize
|
// lblPlateSize
|
||||||
//
|
//
|
||||||
lblPlateSize.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
lblPlateSize.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||||
lblPlateSize.AutoSize = true;
|
lblPlateSize.AutoSize = true;
|
||||||
lblPlateSize.Location = new System.Drawing.Point(6, 112);
|
lblPlateSize.Location = new System.Drawing.Point(6, 112);
|
||||||
@@ -186,9 +186,9 @@ namespace OpenNest.Forms
|
|||||||
lblPlateSize.Size = new System.Drawing.Size(59, 15);
|
lblPlateSize.Size = new System.Drawing.Size(59, 15);
|
||||||
lblPlateSize.TabIndex = 8;
|
lblPlateSize.TabIndex = 8;
|
||||||
lblPlateSize.Text = "Plate Size:";
|
lblPlateSize.Text = "Plate Size:";
|
||||||
//
|
//
|
||||||
// platePanel
|
// platePanel
|
||||||
//
|
//
|
||||||
platePanel.AutoSize = true;
|
platePanel.AutoSize = true;
|
||||||
platePanel.Controls.Add(txtPlateWidth);
|
platePanel.Controls.Add(txtPlateWidth);
|
||||||
platePanel.Controls.Add(lblPlateX);
|
platePanel.Controls.Add(lblPlateX);
|
||||||
@@ -199,17 +199,17 @@ namespace OpenNest.Forms
|
|||||||
platePanel.Size = new System.Drawing.Size(156, 29);
|
platePanel.Size = new System.Drawing.Size(156, 29);
|
||||||
platePanel.TabIndex = 9;
|
platePanel.TabIndex = 9;
|
||||||
platePanel.WrapContents = false;
|
platePanel.WrapContents = false;
|
||||||
//
|
//
|
||||||
// txtPlateWidth
|
// txtPlateWidth
|
||||||
//
|
//
|
||||||
txtPlateWidth.Location = new System.Drawing.Point(3, 3);
|
txtPlateWidth.Location = new System.Drawing.Point(3, 3);
|
||||||
txtPlateWidth.Name = "txtPlateWidth";
|
txtPlateWidth.Name = "txtPlateWidth";
|
||||||
txtPlateWidth.Size = new System.Drawing.Size(60, 23);
|
txtPlateWidth.Size = new System.Drawing.Size(60, 23);
|
||||||
txtPlateWidth.TabIndex = 0;
|
txtPlateWidth.TabIndex = 0;
|
||||||
txtPlateWidth.Text = "60";
|
txtPlateWidth.Text = "60";
|
||||||
//
|
//
|
||||||
// lblPlateX
|
// lblPlateX
|
||||||
//
|
//
|
||||||
lblPlateX.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
lblPlateX.Anchor = System.Windows.Forms.AnchorStyles.Left;
|
||||||
lblPlateX.AutoSize = true;
|
lblPlateX.AutoSize = true;
|
||||||
lblPlateX.Location = new System.Drawing.Point(69, 7);
|
lblPlateX.Location = new System.Drawing.Point(69, 7);
|
||||||
@@ -217,17 +217,17 @@ namespace OpenNest.Forms
|
|||||||
lblPlateX.Size = new System.Drawing.Size(18, 15);
|
lblPlateX.Size = new System.Drawing.Size(18, 15);
|
||||||
lblPlateX.TabIndex = 1;
|
lblPlateX.TabIndex = 1;
|
||||||
lblPlateX.Text = " x ";
|
lblPlateX.Text = " x ";
|
||||||
//
|
//
|
||||||
// txtPlateLength
|
// txtPlateLength
|
||||||
//
|
//
|
||||||
txtPlateLength.Location = new System.Drawing.Point(93, 3);
|
txtPlateLength.Location = new System.Drawing.Point(93, 3);
|
||||||
txtPlateLength.Name = "txtPlateLength";
|
txtPlateLength.Name = "txtPlateLength";
|
||||||
txtPlateLength.Size = new System.Drawing.Size(60, 23);
|
txtPlateLength.Size = new System.Drawing.Size(60, 23);
|
||||||
txtPlateLength.TabIndex = 2;
|
txtPlateLength.TabIndex = 2;
|
||||||
txtPlateLength.Text = "120";
|
txtPlateLength.Text = "120";
|
||||||
//
|
//
|
||||||
// btnAnalyze
|
// btnAnalyze
|
||||||
//
|
//
|
||||||
btnAnalyze.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
btnAnalyze.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||||
tbl.SetColumnSpan(btnAnalyze, 2);
|
tbl.SetColumnSpan(btnAnalyze, 2);
|
||||||
btnAnalyze.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
btnAnalyze.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
||||||
@@ -291,9 +291,9 @@ namespace OpenNest.Forms
|
|||||||
dgvGroups.RowHeadersVisible = false;
|
dgvGroups.RowHeadersVisible = false;
|
||||||
dgvGroups.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
dgvGroups.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
|
||||||
dgvGroups.TabIndex = 0;
|
dgvGroups.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// pnlBottom
|
// pnlBottom
|
||||||
//
|
//
|
||||||
pnlBottom.Controls.Add(lblSummary);
|
pnlBottom.Controls.Add(lblSummary);
|
||||||
pnlBottom.Controls.Add(btnCreateNests);
|
pnlBottom.Controls.Add(btnCreateNests);
|
||||||
pnlBottom.Controls.Add(btnClose);
|
pnlBottom.Controls.Add(btnClose);
|
||||||
@@ -303,9 +303,9 @@ namespace OpenNest.Forms
|
|||||||
pnlBottom.Padding = new System.Windows.Forms.Padding(10);
|
pnlBottom.Padding = new System.Windows.Forms.Padding(10);
|
||||||
pnlBottom.Size = new System.Drawing.Size(804, 50);
|
pnlBottom.Size = new System.Drawing.Size(804, 50);
|
||||||
pnlBottom.TabIndex = 2;
|
pnlBottom.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// lblSummary
|
// lblSummary
|
||||||
//
|
//
|
||||||
lblSummary.AutoSize = true;
|
lblSummary.AutoSize = true;
|
||||||
lblSummary.Dock = System.Windows.Forms.DockStyle.Left;
|
lblSummary.Dock = System.Windows.Forms.DockStyle.Left;
|
||||||
lblSummary.ForeColor = System.Drawing.Color.Gray;
|
lblSummary.ForeColor = System.Drawing.Color.Gray;
|
||||||
@@ -314,9 +314,9 @@ namespace OpenNest.Forms
|
|||||||
lblSummary.Size = new System.Drawing.Size(0, 15);
|
lblSummary.Size = new System.Drawing.Size(0, 15);
|
||||||
lblSummary.TabIndex = 0;
|
lblSummary.TabIndex = 0;
|
||||||
lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
lblSummary.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
|
||||||
//
|
//
|
||||||
// btnCreateNests
|
// btnCreateNests
|
||||||
//
|
//
|
||||||
btnCreateNests.Dock = System.Windows.Forms.DockStyle.Right;
|
btnCreateNests.Dock = System.Windows.Forms.DockStyle.Right;
|
||||||
btnCreateNests.Enabled = false;
|
btnCreateNests.Enabled = false;
|
||||||
btnCreateNests.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
btnCreateNests.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
||||||
@@ -327,9 +327,9 @@ namespace OpenNest.Forms
|
|||||||
btnCreateNests.TabIndex = 1;
|
btnCreateNests.TabIndex = 1;
|
||||||
btnCreateNests.Text = "Create Nests";
|
btnCreateNests.Text = "Create Nests";
|
||||||
btnCreateNests.Click += CreateNests_Click;
|
btnCreateNests.Click += CreateNests_Click;
|
||||||
//
|
//
|
||||||
// btnClose
|
// btnClose
|
||||||
//
|
//
|
||||||
btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||||
btnClose.Dock = System.Windows.Forms.DockStyle.Right;
|
btnClose.Dock = System.Windows.Forms.DockStyle.Right;
|
||||||
btnClose.Location = new System.Drawing.Point(714, 10);
|
btnClose.Location = new System.Drawing.Point(714, 10);
|
||||||
@@ -338,9 +338,9 @@ namespace OpenNest.Forms
|
|||||||
btnClose.TabIndex = 2;
|
btnClose.TabIndex = 2;
|
||||||
btnClose.Text = "Close";
|
btnClose.Text = "Close";
|
||||||
btnClose.Click += BtnClose_Click;
|
btnClose.Click += BtnClose_Click;
|
||||||
//
|
//
|
||||||
// BomImportForm
|
// BomImportForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
CancelButton = btnClose;
|
CancelButton = btnClose;
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ namespace OpenNest.Forms
|
|||||||
public partial class BomImportForm : Form
|
public partial class BomImportForm : Form
|
||||||
{
|
{
|
||||||
private List<BomPartRow> _parts;
|
private List<BomPartRow> _parts;
|
||||||
private Dictionary<string, (double Width, double Length)> _plateSizes;
|
private Dictionary<string, GroupSettings> _groupSettings;
|
||||||
private bool _suppressRegroup;
|
private bool _suppressRegroup;
|
||||||
|
private Nest.PlateSettings _templateDefaults;
|
||||||
|
|
||||||
public Form MdiParentForm { get; set; }
|
public Form MdiParentForm { get; set; }
|
||||||
|
|
||||||
@@ -25,7 +26,38 @@ namespace OpenNest.Forms
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_parts = new List<BomPartRow>();
|
_parts = new List<BomPartRow>();
|
||||||
_plateSizes = new Dictionary<string, (double, double)>();
|
_groupSettings = new Dictionary<string, GroupSettings>();
|
||||||
|
_templateDefaults = LoadTemplateDefaults();
|
||||||
|
ApplyTemplateDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Nest.PlateSettings LoadTemplateDefaults()
|
||||||
|
{
|
||||||
|
var templatePath = Properties.Settings.Default.NestTemplatePath;
|
||||||
|
if (File.Exists(templatePath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nest = new NestReader(templatePath).Read();
|
||||||
|
return nest.PlateDefaults;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback defaults matching CreateDefaultNest
|
||||||
|
return new Nest.PlateSettings
|
||||||
|
{
|
||||||
|
Size = new Geometry.Size(100, 100),
|
||||||
|
Quadrant = 1,
|
||||||
|
PartSpacing = 1,
|
||||||
|
EdgeSpacing = new Spacing(1, 1, 1, 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTemplateDefaults()
|
||||||
|
{
|
||||||
|
txtPlateWidth.Text = _templateDefaults.Size.Width.ToString("0.####");
|
||||||
|
txtPlateLength.Text = _templateDefaults.Size.Length.ToString("0.####");
|
||||||
}
|
}
|
||||||
|
|
||||||
#region File Browsing
|
#region File Browsing
|
||||||
@@ -154,7 +186,7 @@ namespace OpenNest.Forms
|
|||||||
_parts.Add(row);
|
_parts.Add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
_plateSizes.Clear();
|
_groupSettings.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -244,11 +276,11 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
private void RebuildGroups()
|
private void RebuildGroups()
|
||||||
{
|
{
|
||||||
// Save existing plate sizes before rebuilding
|
// Save existing settings before rebuilding
|
||||||
SavePlateSizes();
|
SaveGroupSettings();
|
||||||
|
|
||||||
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var w) ? w : 60;
|
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var w) ? w : _templateDefaults.Size.Width;
|
||||||
var defaultLength = double.TryParse(txtPlateLength.Text, out var l) ? l : 120;
|
var defaultLength = double.TryParse(txtPlateLength.Text, out var l) ? l : _templateDefaults.Size.Length;
|
||||||
|
|
||||||
var groups = _parts
|
var groups = _parts
|
||||||
.Where(p => p.IsEditable
|
.Where(p => p.IsEditable
|
||||||
@@ -270,6 +302,11 @@ namespace OpenNest.Forms
|
|||||||
table.Columns.Add("Total Qty", typeof(int));
|
table.Columns.Add("Total Qty", typeof(int));
|
||||||
table.Columns.Add("Plate Width", typeof(double));
|
table.Columns.Add("Plate Width", typeof(double));
|
||||||
table.Columns.Add("Plate Length", typeof(double));
|
table.Columns.Add("Plate Length", typeof(double));
|
||||||
|
table.Columns.Add("Part Spacing", typeof(double));
|
||||||
|
table.Columns.Add("Edge Left", typeof(double));
|
||||||
|
table.Columns.Add("Edge Bottom", typeof(double));
|
||||||
|
table.Columns.Add("Edge Right", typeof(double));
|
||||||
|
table.Columns.Add("Edge Top", typeof(double));
|
||||||
|
|
||||||
foreach (var group in groups)
|
foreach (var group in groups)
|
||||||
{
|
{
|
||||||
@@ -277,23 +314,27 @@ namespace OpenNest.Forms
|
|||||||
var thickness = group.Key.Thickness;
|
var thickness = group.Key.Thickness;
|
||||||
var key = GroupKey(material, thickness);
|
var key = GroupKey(material, thickness);
|
||||||
|
|
||||||
var plateWidth = _plateSizes.TryGetValue(key, out var size) ? size.Width : defaultWidth;
|
var existing = _groupSettings.TryGetValue(key, out var gs);
|
||||||
var plateLength = _plateSizes.TryGetValue(key, out _) ? size.Length : defaultLength;
|
|
||||||
|
|
||||||
table.Rows.Add(
|
table.Rows.Add(
|
||||||
material,
|
material,
|
||||||
thickness,
|
thickness,
|
||||||
group.Count(),
|
group.Count(),
|
||||||
group.Sum(p => p.Qty ?? 0),
|
group.Sum(p => p.Qty ?? 0),
|
||||||
plateWidth,
|
existing ? gs.PlateWidth : defaultWidth,
|
||||||
plateLength
|
existing ? gs.PlateLength : defaultLength,
|
||||||
|
existing ? gs.PartSpacing : _templateDefaults.PartSpacing,
|
||||||
|
existing ? gs.EdgeLeft : _templateDefaults.EdgeSpacing.Left,
|
||||||
|
existing ? gs.EdgeBottom : _templateDefaults.EdgeSpacing.Bottom,
|
||||||
|
existing ? gs.EdgeRight : _templateDefaults.EdgeSpacing.Right,
|
||||||
|
existing ? gs.EdgeTop : _templateDefaults.EdgeSpacing.Top
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dgvGroups.DataSource = table;
|
dgvGroups.DataSource = table;
|
||||||
|
|
||||||
// Material, Thickness, Parts, Total Qty are read-only
|
// Material, Thickness, Parts, Total Qty are read-only
|
||||||
if (dgvGroups.Columns.Count >= 6)
|
if (dgvGroups.Columns.Count > 0)
|
||||||
{
|
{
|
||||||
dgvGroups.Columns["Material"].ReadOnly = true;
|
dgvGroups.Columns["Material"].ReadOnly = true;
|
||||||
dgvGroups.Columns["Thickness"].ReadOnly = true;
|
dgvGroups.Columns["Thickness"].ReadOnly = true;
|
||||||
@@ -304,22 +345,28 @@ namespace OpenNest.Forms
|
|||||||
btnCreateNests.Enabled = table.Rows.Count > 0;
|
btnCreateNests.Enabled = table.Rows.Count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SavePlateSizes()
|
private void SaveGroupSettings()
|
||||||
{
|
{
|
||||||
if (dgvGroups.DataSource is not DataTable table)
|
if (dgvGroups.DataSource is not DataTable table)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_plateSizes.Clear();
|
_groupSettings.Clear();
|
||||||
foreach (DataRow row in table.Rows)
|
foreach (DataRow row in table.Rows)
|
||||||
{
|
{
|
||||||
var material = row["Material"]?.ToString() ?? "";
|
var material = row["Material"]?.ToString() ?? "";
|
||||||
var thickness = row["Thickness"] is double t ? t : 0;
|
var thickness = row["Thickness"] is double t ? t : 0;
|
||||||
var key = GroupKey(material, thickness);
|
var key = GroupKey(material, thickness);
|
||||||
|
|
||||||
var width = row["Plate Width"] is double pw ? pw : 60;
|
_groupSettings[key] = new GroupSettings
|
||||||
var length = row["Plate Length"] is double pl ? pl : 120;
|
{
|
||||||
|
PlateWidth = row["Plate Width"] is double pw ? pw : _templateDefaults.Size.Width,
|
||||||
_plateSizes[key] = (width, length);
|
PlateLength = row["Plate Length"] is double pl ? pl : _templateDefaults.Size.Length,
|
||||||
|
PartSpacing = row["Part Spacing"] is double ps ? ps : _templateDefaults.PartSpacing,
|
||||||
|
EdgeLeft = row["Edge Left"] is double el ? el : _templateDefaults.EdgeSpacing.Left,
|
||||||
|
EdgeBottom = row["Edge Bottom"] is double eb ? eb : _templateDefaults.EdgeSpacing.Bottom,
|
||||||
|
EdgeRight = row["Edge Right"] is double er ? er : _templateDefaults.EdgeSpacing.Right,
|
||||||
|
EdgeTop = row["Edge Top"] is double et ? et : _templateDefaults.EdgeSpacing.Top,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,11 +403,11 @@ namespace OpenNest.Forms
|
|||||||
if (_parts == null || _parts.Count == 0)
|
if (_parts == null || _parts.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Save latest plate size edits
|
// Save latest group edits
|
||||||
SavePlateSizes();
|
SaveGroupSettings();
|
||||||
|
|
||||||
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var dw) ? dw : 60;
|
var defaultWidth = double.TryParse(txtPlateWidth.Text, out var dw) ? dw : _templateDefaults.Size.Width;
|
||||||
var defaultLength = double.TryParse(txtPlateLength.Text, out var dl) ? dl : 120;
|
var defaultLength = double.TryParse(txtPlateLength.Text, out var dl) ? dl : _templateDefaults.Size.Length;
|
||||||
|
|
||||||
var groups = _parts
|
var groups = _parts
|
||||||
.Where(p => p.IsEditable
|
.Where(p => p.IsEditable
|
||||||
@@ -391,8 +438,14 @@ namespace OpenNest.Forms
|
|||||||
var thickness = group.Key.Thickness;
|
var thickness = group.Key.Thickness;
|
||||||
var key = GroupKey(material, thickness);
|
var key = GroupKey(material, thickness);
|
||||||
|
|
||||||
var plateWidth = _plateSizes.TryGetValue(key, out var size) ? size.Width : defaultWidth;
|
var hasSettings = _groupSettings.TryGetValue(key, out var gs);
|
||||||
var plateLength = _plateSizes.TryGetValue(key, out _) ? size.Length : defaultLength;
|
var plateWidth = hasSettings ? gs.PlateWidth : defaultWidth;
|
||||||
|
var plateLength = hasSettings ? gs.PlateLength : defaultLength;
|
||||||
|
var partSpacing = hasSettings ? gs.PartSpacing : _templateDefaults.PartSpacing;
|
||||||
|
var edgeLeft = hasSettings ? gs.EdgeLeft : _templateDefaults.EdgeSpacing.Left;
|
||||||
|
var edgeBottom = hasSettings ? gs.EdgeBottom : _templateDefaults.EdgeSpacing.Bottom;
|
||||||
|
var edgeRight = hasSettings ? gs.EdgeRight : _templateDefaults.EdgeSpacing.Right;
|
||||||
|
var edgeTop = hasSettings ? gs.EdgeTop : _templateDefaults.EdgeSpacing.Top;
|
||||||
|
|
||||||
var nestName = $"{jobName} - {thickness:0.###} {material}";
|
var nestName = $"{jobName} - {thickness:0.###} {material}";
|
||||||
var nest = new Nest(nestName);
|
var nest = new Nest(nestName);
|
||||||
@@ -401,9 +454,9 @@ namespace OpenNest.Forms
|
|||||||
nest.PlateDefaults.Size = new Geometry.Size(plateWidth, plateLength);
|
nest.PlateDefaults.Size = new Geometry.Size(plateWidth, plateLength);
|
||||||
nest.Thickness = thickness;
|
nest.Thickness = thickness;
|
||||||
nest.Material = new Material(material);
|
nest.Material = new Material(material);
|
||||||
nest.PlateDefaults.Quadrant = 1;
|
nest.PlateDefaults.Quadrant = _templateDefaults.Quadrant;
|
||||||
nest.PlateDefaults.PartSpacing = 1;
|
nest.PlateDefaults.PartSpacing = partSpacing;
|
||||||
nest.PlateDefaults.EdgeSpacing = new Spacing(1, 1, 1, 1);
|
nest.PlateDefaults.EdgeSpacing = new Spacing(edgeLeft, edgeBottom, edgeRight, edgeTop);
|
||||||
|
|
||||||
foreach (var part in group)
|
foreach (var part in group)
|
||||||
{
|
{
|
||||||
@@ -486,4 +539,15 @@ namespace OpenNest.Forms
|
|||||||
public string Status { get; set; }
|
public string Status { get; set; }
|
||||||
public bool IsEditable { get; set; }
|
public bool IsEditable { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class GroupSettings
|
||||||
|
{
|
||||||
|
public double PlateWidth { get; set; }
|
||||||
|
public double PlateLength { get; set; }
|
||||||
|
public double PartSpacing { get; set; }
|
||||||
|
public double EdgeLeft { get; set; }
|
||||||
|
public double EdgeBottom { get; set; }
|
||||||
|
public double EdgeRight { get; set; }
|
||||||
|
public double EdgeTop { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -955,6 +955,15 @@ namespace OpenNest.Forms
|
|||||||
drawingListBox1.Items.RemoveAt(i);
|
drawingListBox1.Items.RemoveAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var dwg in Nest.Drawings.OrderBy(d => d.Name))
|
||||||
|
{
|
||||||
|
if (dwg.Quantity.Required > 0 && dwg.Quantity.Remaining == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!drawingListBox1.Items.Contains(dwg))
|
||||||
|
drawingListBox1.Items.Add(dwg);
|
||||||
|
}
|
||||||
|
|
||||||
drawingListBox1.EndUpdate();
|
drawingListBox1.EndUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ namespace OpenNest.Forms
|
|||||||
//if (GpuEvaluatorFactory.GpuAvailable)
|
//if (GpuEvaluatorFactory.GpuAvailable)
|
||||||
// BestFitCache.CreateEvaluator = (drawing, spacing) => GpuEvaluatorFactory.Create(drawing, spacing);
|
// BestFitCache.CreateEvaluator = (drawing, spacing) => GpuEvaluatorFactory.Create(drawing, spacing);
|
||||||
|
|
||||||
if (GpuEvaluatorFactory.GpuAvailable)
|
//if (GpuEvaluatorFactory.GpuAvailable)
|
||||||
BestFitCache.CreateSlideComputer = () => GpuEvaluatorFactory.CreateSlideComputer();
|
// BestFitCache.CreateSlideComputer = () => GpuEvaluatorFactory.CreateSlideComputer();
|
||||||
|
|
||||||
var enginesDir = Path.Combine(Application.StartupPath, "Engines");
|
var enginesDir = Path.Combine(Application.StartupPath, "Engines");
|
||||||
NestEngineRegistry.LoadPlugins(enginesDir);
|
NestEngineRegistry.LoadPlugins(enginesDir);
|
||||||
|
|||||||
Reference in New Issue
Block a user