feat: add etch mark entities from bend lines to CNC program pipeline
Etch marks for up bends are now real geometry entities on an ETCH layer instead of being drawn dynamically. They flow through the full pipeline: entities → FilterPanel layers → ConvertGeometry (tagged as Scribe) → post-processor sequencing before cut geometry. Also includes ShapeProfile normalization (CW perimeter, CCW cutouts) applied consistently across all import paths, and inward offset support for cutout shapes in overlap/offset polygon calculations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,8 @@ public static class NestRunner
|
||||
if (!importer.GetGeometry(part.DxfPath, out var geometry) || geometry.Count == 0)
|
||||
throw new InvalidOperationException($"Failed to import DXF: {part.DxfPath}");
|
||||
|
||||
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
var name = Path.GetFileNameWithoutExtension(part.DxfPath);
|
||||
var drawing = new Drawing(name);
|
||||
drawing.Program = pgm;
|
||||
|
||||
@@ -255,7 +255,8 @@ static class NestConsole
|
||||
return null;
|
||||
}
|
||||
|
||||
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
|
||||
if (pgm == null)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenNest.Bending
|
||||
{
|
||||
public class Bend
|
||||
{
|
||||
public static readonly Layer EtchLayer = new Layer("ETCH")
|
||||
{
|
||||
Color = Color.Green,
|
||||
IsVisible = true
|
||||
};
|
||||
|
||||
private const double DefaultEtchLength = 1.0;
|
||||
|
||||
public Vector StartPoint { get; set; }
|
||||
public Vector EndPoint { get; set; }
|
||||
public BendDirection Direction { get; set; }
|
||||
@@ -29,6 +39,52 @@ namespace OpenNest.Bending
|
||||
/// </summary>
|
||||
public double LineAngle => StartPoint.AngleTo(EndPoint);
|
||||
|
||||
/// <summary>
|
||||
/// Generates etch mark entities for this bend (up bends only).
|
||||
/// Returns 1" dashes at each end of the bend line, or the full line if shorter than 3".
|
||||
/// </summary>
|
||||
public List<Line> GetEtchEntities(double etchLength = DefaultEtchLength)
|
||||
{
|
||||
var result = new List<Line>();
|
||||
if (Direction != BendDirection.Up)
|
||||
return result;
|
||||
|
||||
var length = Length;
|
||||
|
||||
if (length < etchLength * 3.0)
|
||||
{
|
||||
result.Add(CreateEtchLine(StartPoint, EndPoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
var angle = StartPoint.AngleTo(EndPoint);
|
||||
var dx = System.Math.Cos(angle) * etchLength;
|
||||
var dy = System.Math.Sin(angle) * etchLength;
|
||||
|
||||
result.Add(CreateEtchLine(StartPoint, new Vector(StartPoint.X + dx, StartPoint.Y + dy)));
|
||||
result.Add(CreateEtchLine(new Vector(EndPoint.X - dx, EndPoint.Y - dy), EndPoint));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes existing etch entities from the list and regenerates from the given bends.
|
||||
/// </summary>
|
||||
public static void UpdateEtchEntities(List<Entity> entities, List<Bend> bends)
|
||||
{
|
||||
entities.RemoveAll(e => e.Layer == EtchLayer);
|
||||
if (bends == null) return;
|
||||
|
||||
foreach (var bend in bends)
|
||||
entities.AddRange(bend.GetEtchEntities());
|
||||
}
|
||||
|
||||
private static Line CreateEtchLine(Vector start, Vector end)
|
||||
{
|
||||
return new Line(start, end) { Layer = EtchLayer, Color = Color.Green };
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var dir = Direction.ToString();
|
||||
|
||||
@@ -108,7 +108,10 @@ namespace OpenNest.Converters
|
||||
if (line.StartPoint != lastpt)
|
||||
pgm.MoveTo(line.StartPoint);
|
||||
|
||||
pgm.LineTo(line.EndPoint);
|
||||
var move = new LinearMove(line.EndPoint);
|
||||
if (string.Equals(line.Layer?.Name, "ETCH", System.StringComparison.OrdinalIgnoreCase))
|
||||
move.Layer = LayerType.Scribe;
|
||||
pgm.Codes.Add(move);
|
||||
|
||||
lastpt = line.EndPoint;
|
||||
return lastpt;
|
||||
|
||||
@@ -598,6 +598,41 @@ namespace OpenNest.Geometry
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape inward by the given distance.
|
||||
/// Normalizes to CCW winding before offsetting Left (which is inward for CCW),
|
||||
/// making the method independent of the original contour winding direction.
|
||||
/// </summary>
|
||||
public Shape OffsetInward(double distance)
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (poly == null || poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Create a reversed copy to avoid mutating shared entity objects.
|
||||
var copy = new Shape();
|
||||
|
||||
for (var i = Entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
switch (Entities[i])
|
||||
{
|
||||
case Line l:
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return copy.OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the shape to the given point.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -41,5 +42,52 @@ namespace OpenNest.Geometry
|
||||
public Shape Perimeter { get; set; }
|
||||
|
||||
public List<Shape> Cutouts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensures CNC-standard winding: perimeter CW (kerf left = outward),
|
||||
/// cutouts CCW (kerf left = inward). Reverses contours in-place as needed.
|
||||
/// </summary>
|
||||
public void NormalizeWinding()
|
||||
{
|
||||
EnsureWinding(Perimeter, RotationType.CW);
|
||||
|
||||
foreach (var cutout in Cutouts)
|
||||
EnsureWinding(cutout, RotationType.CCW);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the entities in normalized winding order (perimeter first, then cutouts).
|
||||
/// </summary>
|
||||
public List<Entity> ToNormalizedEntities()
|
||||
{
|
||||
NormalizeWinding();
|
||||
var result = new List<Entity>(Perimeter.Entities);
|
||||
|
||||
foreach (var cutout in Cutouts)
|
||||
result.AddRange(cutout.Entities);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convenience method: builds a ShapeProfile from raw entities,
|
||||
/// normalizes winding, and returns the corrected entity list.
|
||||
/// </summary>
|
||||
public static List<Entity> NormalizeEntities(IEnumerable<Entity> entities)
|
||||
{
|
||||
var profile = new ShapeProfile(entities.ToList());
|
||||
return profile.ToNormalizedEntities();
|
||||
}
|
||||
|
||||
private static void EnsureWinding(Shape shape, RotationType desired)
|
||||
{
|
||||
var poly = shape.ToPolygon();
|
||||
|
||||
if (poly != null && poly.Vertices.Count >= 3
|
||||
&& poly.RotationDirection() != desired)
|
||||
{
|
||||
shape.Reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,23 +42,17 @@ namespace OpenNest
|
||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, double chordTolerance = 0.001)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
var lines = new List<Line>();
|
||||
var totalSpacing = spacing + chordTolerance;
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
// Add chord tolerance to compensate for inscribed polygon chords
|
||||
// being inside the actual offset arcs.
|
||||
var offsetEntity = shape.OffsetOutward(spacing + chordTolerance);
|
||||
AddOffsetLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance, part.Location);
|
||||
|
||||
if (offsetEntity == null)
|
||||
continue;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(chordTolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
polygon.Offset(part.Location);
|
||||
lines.AddRange(polygon.ToLines());
|
||||
}
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetLines(lines, cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance, part.Location);
|
||||
|
||||
return lines;
|
||||
}
|
||||
@@ -66,21 +60,17 @@ namespace OpenNest
|
||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, PushDirection facingDirection, double chordTolerance = 0.001)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
var lines = new List<Line>();
|
||||
var totalSpacing = spacing + chordTolerance;
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
var offsetEntity = shape.OffsetOutward(spacing + chordTolerance);
|
||||
AddOffsetDirectionalLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
|
||||
if (offsetEntity == null)
|
||||
continue;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(chordTolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
polygon.Offset(part.Location);
|
||||
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
||||
}
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetDirectionalLines(lines, cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
|
||||
return lines;
|
||||
}
|
||||
@@ -104,21 +94,17 @@ namespace OpenNest
|
||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, Vector facingDirection, double chordTolerance = 0.001)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
var lines = new List<Line>();
|
||||
var totalSpacing = spacing + chordTolerance;
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
var offsetEntity = shape.OffsetOutward(spacing + chordTolerance);
|
||||
AddOffsetDirectionalLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
|
||||
if (offsetEntity == null)
|
||||
continue;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(chordTolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
polygon.Offset(part.Location);
|
||||
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
||||
}
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetDirectionalLines(lines, cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
|
||||
return lines;
|
||||
}
|
||||
@@ -189,5 +175,41 @@ namespace OpenNest
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static void AddOffsetLines(List<Line> lines, Shape offsetEntity,
|
||||
double chordTolerance, Vector location)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(chordTolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
polygon.Offset(location);
|
||||
lines.AddRange(polygon.ToLines());
|
||||
}
|
||||
|
||||
private static void AddOffsetDirectionalLines(List<Line> lines, Shape offsetEntity,
|
||||
double chordTolerance, Vector location, PushDirection facingDirection)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(chordTolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
polygon.Offset(location);
|
||||
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
||||
}
|
||||
|
||||
private static void AddOffsetDirectionalLines(List<Line> lines, Shape offsetEntity,
|
||||
double chordTolerance, Vector location, Vector facingDirection)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(chordTolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
polygon.Offset(location);
|
||||
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,18 +22,11 @@ namespace OpenNest.Engine.BestFit
|
||||
if (perimeter == null)
|
||||
return new PolygonExtractionResult(null, Vector.Zero);
|
||||
|
||||
// Inflate by half-spacing if spacing is non-zero.
|
||||
// Detect winding direction to choose the correct outward offset side.
|
||||
var outwardSide = OffsetSide.Right;
|
||||
if (halfSpacing > 0)
|
||||
{
|
||||
var testPoly = perimeter.ToPolygon();
|
||||
if (testPoly.Vertices.Count >= 3 && testPoly.RotationDirection() == RotationType.CW)
|
||||
outwardSide = OffsetSide.Left;
|
||||
}
|
||||
// Ensure CW winding for correct outward offset direction.
|
||||
definedShape.NormalizeWinding();
|
||||
|
||||
var inflated = halfSpacing > 0
|
||||
? (perimeter.OffsetEntity(halfSpacing, outwardSide) as Shape ?? perimeter)
|
||||
? (perimeter.OffsetOutward(halfSpacing) ?? perimeter)
|
||||
: perimeter;
|
||||
|
||||
// Convert to polygon with circumscribed arcs for tight nesting.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using ModelContextProtocol.Server;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
using OpenNest.Shapes;
|
||||
using System.ComponentModel;
|
||||
@@ -103,7 +104,8 @@ namespace OpenNest.Mcp.Tools
|
||||
if (geometry.Count == 0)
|
||||
return "Error: no geometry found in DXF file";
|
||||
|
||||
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
|
||||
if (pgm == null)
|
||||
return "Error: failed to convert geometry to program";
|
||||
|
||||
@@ -151,7 +151,8 @@ int RunDataCollection(string dir, string dbPath, string saveDir, double s, strin
|
||||
}
|
||||
|
||||
var drawing = new Drawing(Path.GetFileName(file));
|
||||
drawing.Program = OpenNest.Converters.ConvertGeometry.ToProgram(entities);
|
||||
var normalized = ShapeProfile.NormalizeEntities(entities);
|
||||
drawing.Program = OpenNest.Converters.ConvertGeometry.ToProgram(normalized);
|
||||
drawing.UpdateArea();
|
||||
drawing.Color = PartColors[colorIndex % PartColors.Length];
|
||||
colorIndex++;
|
||||
|
||||
@@ -112,8 +112,6 @@ namespace OpenNest.Controls
|
||||
DrawEntity(e.Graphics, entity, pen);
|
||||
}
|
||||
|
||||
DrawEtchMarks(e.Graphics);
|
||||
|
||||
if (SimplifierPreview != null)
|
||||
{
|
||||
// Draw tolerance zone (offset lines each side of original geometry)
|
||||
@@ -240,46 +238,6 @@ namespace OpenNest.Controls
|
||||
private static bool IsEtchLayer(Layer layer) =>
|
||||
string.Equals(layer?.Name, "ETCH", System.StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
private void DrawEtchMarks(Graphics g)
|
||||
{
|
||||
if (Bends == null || Bends.Count == 0)
|
||||
return;
|
||||
|
||||
using var etchPen = new Pen(Color.Green, 1.5f);
|
||||
var etchLength = 1.0;
|
||||
|
||||
foreach (var bend in Bends)
|
||||
{
|
||||
if (bend.Direction != BendDirection.Up)
|
||||
continue;
|
||||
|
||||
var start = bend.StartPoint;
|
||||
var end = bend.EndPoint;
|
||||
var length = bend.Length;
|
||||
|
||||
if (length < etchLength * 3.0)
|
||||
{
|
||||
var pt1 = PointWorldToGraph(start);
|
||||
var pt2 = PointWorldToGraph(end);
|
||||
g.DrawLine(etchPen, pt1, pt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var angle = start.AngleTo(end);
|
||||
var dx = System.Math.Cos(angle) * etchLength;
|
||||
var dy = System.Math.Sin(angle) * etchLength;
|
||||
|
||||
var s1 = PointWorldToGraph(start);
|
||||
var e1 = PointWorldToGraph(new Vector(start.X + dx, start.Y + dy));
|
||||
g.DrawLine(etchPen, s1, e1);
|
||||
|
||||
var s2 = PointWorldToGraph(end);
|
||||
var e2 = PointWorldToGraph(new Vector(end.X - dx, end.Y - dy));
|
||||
g.DrawLine(etchPen, s2, e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBendLines(Graphics g)
|
||||
{
|
||||
if (Bends == null || Bends.Count == 0)
|
||||
|
||||
@@ -584,6 +584,7 @@ namespace OpenNest.Controls
|
||||
|
||||
part.Draw(g, (i + 1).ToString());
|
||||
DrawBendLines(g, part.BasePart);
|
||||
DrawEtchMarks(g, part.BasePart);
|
||||
DrawGrainWarning(g, part.BasePart);
|
||||
}
|
||||
|
||||
@@ -657,6 +658,58 @@ namespace OpenNest.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEtchMarks(Graphics g, Part part)
|
||||
{
|
||||
if (!ShowBendLines || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
return;
|
||||
|
||||
using var etchPen = new Pen(Color.Green, 1.5f);
|
||||
var etchLength = 1.0;
|
||||
|
||||
foreach (var bend in part.BaseDrawing.Bends)
|
||||
{
|
||||
if (bend.Direction != BendDirection.Up)
|
||||
continue;
|
||||
|
||||
var start = bend.StartPoint;
|
||||
var end = bend.EndPoint;
|
||||
|
||||
// Apply part rotation
|
||||
if (part.Rotation != 0)
|
||||
{
|
||||
start = start.Rotate(part.Rotation);
|
||||
end = end.Rotate(part.Rotation);
|
||||
}
|
||||
|
||||
// Apply part offset
|
||||
start = start + part.Location;
|
||||
end = end + part.Location;
|
||||
|
||||
var length = bend.Length;
|
||||
var angle = bend.StartPoint.AngleTo(bend.EndPoint) + part.Rotation;
|
||||
|
||||
if (length < etchLength * 3.0)
|
||||
{
|
||||
var pt1 = PointWorldToGraph(start);
|
||||
var pt2 = PointWorldToGraph(end);
|
||||
g.DrawLine(etchPen, pt1, pt2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dx = System.Math.Cos(angle) * etchLength;
|
||||
var dy = System.Math.Sin(angle) * etchLength;
|
||||
|
||||
var s1 = PointWorldToGraph(start);
|
||||
var e1 = PointWorldToGraph(new Vector(start.X + dx, start.Y + dy));
|
||||
g.DrawLine(etchPen, s1, e1);
|
||||
|
||||
var s2 = PointWorldToGraph(end);
|
||||
var e2 = PointWorldToGraph(new Vector(end.X - dx, end.Y - dy));
|
||||
g.DrawLine(etchPen, s2, e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGrainWarning(Graphics g, Part part)
|
||||
{
|
||||
if (!ShowBendLines || Plate == null || part.BaseDrawing.Bends == null || part.BaseDrawing.Bends.Count == 0)
|
||||
|
||||
@@ -424,7 +424,8 @@ namespace OpenNest.Forms
|
||||
drawing.Quantity.Required = part.Qty ?? 1;
|
||||
drawing.Material = new Material(material);
|
||||
|
||||
var pgm = ConvertGeometry.ToProgram(result.Entities);
|
||||
var normalized = ShapeProfile.NormalizeEntities(result.Entities);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
|
||||
if (pgm.Codes.Count > 0 && pgm[0].Type == CodeType.RapidMove)
|
||||
{
|
||||
|
||||
@@ -81,6 +81,8 @@ namespace OpenNest.Forms
|
||||
?? new List<Bend>();
|
||||
}
|
||||
|
||||
Bend.UpdateEtchEntities(result.Entities, bends);
|
||||
|
||||
var item = new FileListItem
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(file),
|
||||
@@ -245,6 +247,9 @@ namespace OpenNest.Forms
|
||||
bend.SourceEntity.IsVisible = true;
|
||||
|
||||
item.Bends.RemoveAt(index);
|
||||
Bend.UpdateEtchEntities(item.Entities, item.Bends);
|
||||
entityView1.Entities.Clear();
|
||||
entityView1.Entities.AddRange(item.Entities);
|
||||
entityView1.Bends = item.Bends;
|
||||
entityView1.SelectedBendIndex = -1;
|
||||
filterPanel.LoadItem(item.Entities, item.Bends);
|
||||
@@ -281,16 +286,8 @@ namespace OpenNest.Forms
|
||||
var entities = item.Entities.Where(en => en.Layer.IsVisible && en.IsVisible).ToList();
|
||||
if (entities.Count == 0) return;
|
||||
|
||||
var shape = new ShapeProfile(entities);
|
||||
SetRotation(shape.Perimeter, RotationType.CW);
|
||||
foreach (var cutout in shape.Cutouts)
|
||||
SetRotation(cutout, RotationType.CCW);
|
||||
|
||||
var drawEntities = new List<Entity>();
|
||||
drawEntities.AddRange(shape.Perimeter.Entities);
|
||||
shape.Cutouts.ForEach(c => drawEntities.AddRange(c.Entities));
|
||||
|
||||
var pgm = ConvertGeometry.ToProgram(drawEntities);
|
||||
var normalized = ShapeProfile.NormalizeEntities(entities);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
var originOffset = Vector.Zero;
|
||||
if (pgm.Codes.Count > 0 && pgm[0].Type == CodeType.RapidMove)
|
||||
{
|
||||
@@ -395,6 +392,9 @@ namespace OpenNest.Forms
|
||||
|
||||
line.IsVisible = false;
|
||||
item.Bends.Add(bend);
|
||||
Bend.UpdateEtchEntities(item.Entities, item.Bends);
|
||||
entityView1.Entities.Clear();
|
||||
entityView1.Entities.AddRange(item.Entities);
|
||||
entityView1.Bends = item.Bends;
|
||||
filterPanel.LoadItem(item.Entities, item.Bends);
|
||||
entityView1.Invalidate();
|
||||
@@ -560,18 +560,8 @@ namespace OpenNest.Forms
|
||||
if (item.Bends != null)
|
||||
drawing.Bends.AddRange(item.Bends);
|
||||
|
||||
var shape = new ShapeProfile(entities);
|
||||
|
||||
SetRotation(shape.Perimeter, RotationType.CW);
|
||||
|
||||
foreach (var cutout in shape.Cutouts)
|
||||
SetRotation(cutout, RotationType.CCW);
|
||||
|
||||
entities = new List<Entity>();
|
||||
entities.AddRange(shape.Perimeter.Entities);
|
||||
shape.Cutouts.ForEach(cutout => entities.AddRange(cutout.Entities));
|
||||
|
||||
var pgm = ConvertGeometry.ToProgram(entities);
|
||||
var normalized = ShapeProfile.NormalizeEntities(entities);
|
||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||
var firstCode = pgm[0];
|
||||
|
||||
if (firstCode.Type == CodeType.RapidMove)
|
||||
@@ -605,15 +595,6 @@ namespace OpenNest.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetRotation(Shape shape, RotationType rotation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = shape.ToPolygon(3).RotationDirection();
|
||||
if (dir != rotation) shape.Reverse();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static Color GetNextColor()
|
||||
{
|
||||
|
||||
@@ -178,32 +178,36 @@ namespace OpenNest
|
||||
{
|
||||
var result = new List<PointF[]>();
|
||||
var entities = ConvertProgram.ToGeometry(BasePart.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
var offsetEntity = shape.OffsetOutward(spacing);
|
||||
AddOffsetPolygon(result, profile.Perimeter.OffsetOutward(spacing), tolerance);
|
||||
|
||||
if (offsetEntity == null)
|
||||
continue;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(tolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
|
||||
if (polygon.Vertices.Count < 2)
|
||||
continue;
|
||||
|
||||
var pts = new PointF[polygon.Vertices.Count];
|
||||
|
||||
for (var j = 0; j < pts.Length; j++)
|
||||
pts[j] = new PointF((float)polygon.Vertices[j].X, (float)polygon.Vertices[j].Y);
|
||||
|
||||
result.Add(pts);
|
||||
}
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetPolygon(result, cutout.OffsetInward(spacing), tolerance);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void AddOffsetPolygon(List<PointF[]> result, Shape offsetEntity, double tolerance)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
|
||||
var polygon = offsetEntity.ToPolygonWithTolerance(tolerance);
|
||||
polygon.RemoveSelfIntersections();
|
||||
|
||||
if (polygon.Vertices.Count < 2)
|
||||
return;
|
||||
|
||||
var pts = new PointF[polygon.Vertices.Count];
|
||||
|
||||
for (var j = 0; j < pts.Length; j++)
|
||||
pts[j] = new PointF((float)polygon.Vertices[j].X, (float)polygon.Vertices[j].Y);
|
||||
|
||||
result.Add(pts);
|
||||
}
|
||||
|
||||
private void RebuildOffsetPath(Matrix matrix)
|
||||
{
|
||||
OffsetPath?.Dispose();
|
||||
|
||||
Reference in New Issue
Block a user