diff --git a/OpenNest.Api/NestRunner.cs b/OpenNest.Api/NestRunner.cs
index ccca7bd..f48ba2c 100644
--- a/OpenNest.Api/NestRunner.cs
+++ b/OpenNest.Api/NestRunner.cs
@@ -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;
diff --git a/OpenNest.Console/Program.cs b/OpenNest.Console/Program.cs
index 3d5dfa6..18fb896 100644
--- a/OpenNest.Console/Program.cs
+++ b/OpenNest.Console/Program.cs
@@ -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)
{
diff --git a/OpenNest.Core/Bending/Bend.cs b/OpenNest.Core/Bending/Bend.cs
index 98114da..a1ef2d3 100644
--- a/OpenNest.Core/Bending/Bend.cs
+++ b/OpenNest.Core/Bending/Bend.cs
@@ -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
///
public double LineAngle => StartPoint.AngleTo(EndPoint);
+ ///
+ /// 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".
+ ///
+ public List GetEtchEntities(double etchLength = DefaultEtchLength)
+ {
+ var result = new List();
+ 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;
+ }
+
+ ///
+ /// Removes existing etch entities from the list and regenerates from the given bends.
+ ///
+ public static void UpdateEtchEntities(List entities, List 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();
diff --git a/OpenNest.Core/Converters/ConvertGeometry.cs b/OpenNest.Core/Converters/ConvertGeometry.cs
index 8c89945..25c616a 100644
--- a/OpenNest.Core/Converters/ConvertGeometry.cs
+++ b/OpenNest.Core/Converters/ConvertGeometry.cs
@@ -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;
diff --git a/OpenNest.Core/Geometry/Shape.cs b/OpenNest.Core/Geometry/Shape.cs
index 3f984f8..52190c8 100644
--- a/OpenNest.Core/Geometry/Shape.cs
+++ b/OpenNest.Core/Geometry/Shape.cs
@@ -598,6 +598,41 @@ namespace OpenNest.Geometry
return result;
}
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+
///
/// Gets the closest point on the shape to the given point.
///
diff --git a/OpenNest.Core/Geometry/ShapeProfile.cs b/OpenNest.Core/Geometry/ShapeProfile.cs
index 8c1ce33..23928e6 100644
--- a/OpenNest.Core/Geometry/ShapeProfile.cs
+++ b/OpenNest.Core/Geometry/ShapeProfile.cs
@@ -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 Cutouts { get; set; }
+
+ ///
+ /// Ensures CNC-standard winding: perimeter CW (kerf left = outward),
+ /// cutouts CCW (kerf left = inward). Reverses contours in-place as needed.
+ ///
+ public void NormalizeWinding()
+ {
+ EnsureWinding(Perimeter, RotationType.CW);
+
+ foreach (var cutout in Cutouts)
+ EnsureWinding(cutout, RotationType.CCW);
+ }
+
+ ///
+ /// Returns the entities in normalized winding order (perimeter first, then cutouts).
+ ///
+ public List ToNormalizedEntities()
+ {
+ NormalizeWinding();
+ var result = new List(Perimeter.Entities);
+
+ foreach (var cutout in Cutouts)
+ result.AddRange(cutout.Entities);
+
+ return result;
+ }
+
+ ///
+ /// Convenience method: builds a ShapeProfile from raw entities,
+ /// normalizes winding, and returns the corrected entity list.
+ ///
+ public static List NormalizeEntities(IEnumerable 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();
+ }
+ }
}
}
diff --git a/OpenNest.Core/PartGeometry.cs b/OpenNest.Core/PartGeometry.cs
index 7d98d9f..43bdfb4 100644
--- a/OpenNest.Core/PartGeometry.cs
+++ b/OpenNest.Core/PartGeometry.cs
@@ -42,23 +42,17 @@ namespace OpenNest
public static List 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();
+ 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 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();
+ 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 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();
+ 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 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 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 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));
+ }
}
}
diff --git a/OpenNest.Engine/BestFit/PolygonHelper.cs b/OpenNest.Engine/BestFit/PolygonHelper.cs
index e803912..087cd97 100644
--- a/OpenNest.Engine/BestFit/PolygonHelper.cs
+++ b/OpenNest.Engine/BestFit/PolygonHelper.cs
@@ -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.
diff --git a/OpenNest.Mcp/Tools/InputTools.cs b/OpenNest.Mcp/Tools/InputTools.cs
index 4bf5000..d06ce18 100644
--- a/OpenNest.Mcp/Tools/InputTools.cs
+++ b/OpenNest.Mcp/Tools/InputTools.cs
@@ -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";
diff --git a/OpenNest.Training/Program.cs b/OpenNest.Training/Program.cs
index a584169..7e4e8fe 100644
--- a/OpenNest.Training/Program.cs
+++ b/OpenNest.Training/Program.cs
@@ -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++;
diff --git a/OpenNest/Controls/EntityView.cs b/OpenNest/Controls/EntityView.cs
index 9932dc2..d27231d 100644
--- a/OpenNest/Controls/EntityView.cs
+++ b/OpenNest/Controls/EntityView.cs
@@ -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)
diff --git a/OpenNest/Controls/PlateView.cs b/OpenNest/Controls/PlateView.cs
index 0971bd2..797167e 100644
--- a/OpenNest/Controls/PlateView.cs
+++ b/OpenNest/Controls/PlateView.cs
@@ -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)
diff --git a/OpenNest/Forms/BomImportForm.cs b/OpenNest/Forms/BomImportForm.cs
index 9d33466..23f5003 100644
--- a/OpenNest/Forms/BomImportForm.cs
+++ b/OpenNest/Forms/BomImportForm.cs
@@ -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)
{
diff --git a/OpenNest/Forms/CadConverterForm.cs b/OpenNest/Forms/CadConverterForm.cs
index c09f33b..77ea09f 100644
--- a/OpenNest/Forms/CadConverterForm.cs
+++ b/OpenNest/Forms/CadConverterForm.cs
@@ -81,6 +81,8 @@ namespace OpenNest.Forms
?? new List();
}
+ 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();
- 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();
- 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()
{
diff --git a/OpenNest/LayoutPart.cs b/OpenNest/LayoutPart.cs
index 44106ae..ccefe1a 100644
--- a/OpenNest/LayoutPart.cs
+++ b/OpenNest/LayoutPart.cs
@@ -178,32 +178,36 @@ namespace OpenNest
{
var result = new List();
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 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();