Compare commits
6 Commits
3e96c62f33
...
838a247ef9
| Author | SHA1 | Date | |
|---|---|---|---|
| 838a247ef9 | |||
| a5e5e78c4e | |||
| c386e462b2 | |||
| 2c0457d503 | |||
| b03b3eb4d9 | |||
| 29c2872819 |
@@ -267,6 +267,13 @@ namespace OpenNest.Geometry
|
||||
get { return Diameter * System.Math.PI * SweepAngle() / Angle.TwoPI; }
|
||||
}
|
||||
|
||||
public override Entity Clone()
|
||||
{
|
||||
var copy = new Arc(center, radius, startAngle, endAngle, reversed);
|
||||
CopyBaseTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction.
|
||||
/// </summary>
|
||||
|
||||
@@ -165,6 +165,13 @@ namespace OpenNest.Geometry
|
||||
get { return Circumference(); }
|
||||
}
|
||||
|
||||
public override Entity Clone()
|
||||
{
|
||||
var copy = new Circle(center, radius) { Rotation = Rotation };
|
||||
CopyBaseTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction.
|
||||
/// </summary>
|
||||
|
||||
@@ -251,6 +251,23 @@ namespace OpenNest.Geometry
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Shape shape, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep copy of the entity with a new Id.
|
||||
/// </summary>
|
||||
public abstract Entity Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Copies common Entity properties from this instance to the target.
|
||||
/// </summary>
|
||||
protected void CopyBaseTo(Entity target)
|
||||
{
|
||||
target.Color = Color;
|
||||
target.Layer = Layer;
|
||||
target.LineTypeName = LineTypeName;
|
||||
target.IsVisible = IsVisible;
|
||||
target.Tag = Tag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
@@ -259,6 +276,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
public static class EntityExtensions
|
||||
{
|
||||
public static List<Entity> CloneAll(this IEnumerable<Entity> entities)
|
||||
{
|
||||
var result = new List<Entity>();
|
||||
foreach (var e in entities)
|
||||
result.Add(e.Clone());
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Vector> CollectPoints(this IEnumerable<Entity> entities)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
|
||||
@@ -257,6 +257,13 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity Clone()
|
||||
{
|
||||
var copy = new Line(pt1, pt2);
|
||||
CopyBaseTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reversed the line.
|
||||
/// </summary>
|
||||
|
||||
@@ -168,6 +168,13 @@ namespace OpenNest.Geometry
|
||||
get { return Perimeter(); }
|
||||
}
|
||||
|
||||
public override Entity Clone()
|
||||
{
|
||||
var copy = new Polygon { Vertices = new List<Vector>(Vertices) };
|
||||
CopyBaseTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction of the polygon.
|
||||
/// </summary>
|
||||
|
||||
@@ -349,6 +349,15 @@ namespace OpenNest.Geometry
|
||||
return polygon;
|
||||
}
|
||||
|
||||
public override Entity Clone()
|
||||
{
|
||||
var copy = new Shape();
|
||||
foreach (var e in Entities)
|
||||
copy.Entities.Add(e.Clone());
|
||||
CopyBaseTo(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction of the shape.
|
||||
/// </summary>
|
||||
|
||||
@@ -75,7 +75,8 @@ namespace OpenNest.Geometry
|
||||
/// </summary>
|
||||
public static List<Entity> NormalizeEntities(IEnumerable<Entity> entities)
|
||||
{
|
||||
var profile = new ShapeProfile(entities.ToList());
|
||||
var cloned = entities.CloneAll();
|
||||
var profile = new ShapeProfile(cloned);
|
||||
return profile.ToNormalizedEntities();
|
||||
}
|
||||
|
||||
|
||||
@@ -306,49 +306,38 @@ namespace OpenNest.Geometry
|
||||
var minDist = double.MaxValue;
|
||||
var vx = vertex.X;
|
||||
var vy = vertex.Y;
|
||||
var horizontal = IsHorizontalDirection(direction);
|
||||
|
||||
// Pruning: edges are sorted by their perpendicular min-coordinate in PartBoundary.
|
||||
if (direction == PushDirection.Left || direction == PushDirection.Right)
|
||||
// Pruning: edges are sorted by their perpendicular min-coordinate.
|
||||
// For horizontal push, prune by Y range; for vertical push, prune by X range.
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
{
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
var e1 = edges[i].start + edgeOffset;
|
||||
var e2 = edges[i].end + edgeOffset;
|
||||
|
||||
double perpValue, edgeMin, edgeMax;
|
||||
if (horizontal)
|
||||
{
|
||||
var e1 = edges[i].start + edgeOffset;
|
||||
var e2 = edges[i].end + edgeOffset;
|
||||
|
||||
var minY = e1.Y < e2.Y ? e1.Y : e2.Y;
|
||||
var maxY = e1.Y > e2.Y ? e1.Y : e2.Y;
|
||||
|
||||
// Since edges are sorted by minY, if vy < minY, then vy < all subsequent minY.
|
||||
if (vy < minY - Tolerance.Epsilon)
|
||||
break;
|
||||
|
||||
if (vy > maxY + Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
perpValue = vy;
|
||||
edgeMin = e1.Y < e2.Y ? e1.Y : e2.Y;
|
||||
edgeMax = e1.Y > e2.Y ? e1.Y : e2.Y;
|
||||
}
|
||||
}
|
||||
else // Up/Down
|
||||
{
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
else
|
||||
{
|
||||
var e1 = edges[i].start + edgeOffset;
|
||||
var e2 = edges[i].end + edgeOffset;
|
||||
|
||||
var minX = e1.X < e2.X ? e1.X : e2.X;
|
||||
var maxX = e1.X > e2.X ? e1.X : e2.X;
|
||||
|
||||
// Since edges are sorted by minX, if vx < minX, then vx < all subsequent minX.
|
||||
if (vx < minX - Tolerance.Epsilon)
|
||||
break;
|
||||
|
||||
if (vx > maxX + Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
perpValue = vx;
|
||||
edgeMin = e1.X < e2.X ? e1.X : e2.X;
|
||||
edgeMax = e1.X > e2.X ? e1.X : e2.X;
|
||||
}
|
||||
|
||||
// Since edges are sorted by edgeMin, if perpValue < edgeMin, all subsequent edges are also past.
|
||||
if (perpValue < edgeMin - Tolerance.Epsilon)
|
||||
break;
|
||||
|
||||
if (perpValue > edgeMax + Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
@@ -642,19 +631,46 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
for (var i = 0; i < arcEntities.Count; i++)
|
||||
{
|
||||
if (arcEntities[i] is Arc arc)
|
||||
if (arcEntities[i] is not Arc arc)
|
||||
continue;
|
||||
|
||||
var cx = arc.Center.X;
|
||||
var cy = arc.Center.Y;
|
||||
var r = arc.Radius;
|
||||
|
||||
for (var j = 0; j < lineEntities.Count; j++)
|
||||
{
|
||||
for (var j = 0; j < lineEntities.Count; j++)
|
||||
if (lineEntities[j] is not Line line)
|
||||
continue;
|
||||
|
||||
var p1x = line.pt1.X;
|
||||
var p1y = line.pt1.Y;
|
||||
var ex = line.pt2.X - p1x;
|
||||
var ey = line.pt2.Y - p1y;
|
||||
|
||||
var det = ex * dirY - ey * dirX;
|
||||
if (System.Math.Abs(det) < Tolerance.Epsilon)
|
||||
continue;
|
||||
|
||||
// The directional distance from an arc point at angle θ to the
|
||||
// line is t(θ) = [A + r·(ey·cosθ − ex·sinθ)] / det.
|
||||
// dt/dθ = 0 at θ = atan2(−ex, ey) and θ + π.
|
||||
var theta1 = Angle.NormalizeRad(System.Math.Atan2(-ex, ey));
|
||||
var theta2 = Angle.NormalizeRad(theta1 + System.Math.PI);
|
||||
|
||||
for (var k = 0; k < 2; k++)
|
||||
{
|
||||
if (lineEntities[j] is Line line)
|
||||
{
|
||||
var linePt = line.ClosestPointTo(arc.Center);
|
||||
var arcPt = arc.ClosestPointTo(linePt);
|
||||
var d = RayEdgeDistance(arcPt.X, arcPt.Y,
|
||||
line.pt1.X, line.pt1.Y, line.pt2.X, line.pt2.Y,
|
||||
dirX, dirY);
|
||||
if (d < minDist) { minDist = d; if (d <= 0) return 0; }
|
||||
}
|
||||
var theta = k == 0 ? theta1 : theta2;
|
||||
|
||||
if (!Angle.IsBetweenRad(theta, arc.StartAngle, arc.EndAngle, arc.IsReversed))
|
||||
continue;
|
||||
|
||||
var qx = cx + r * System.Math.Cos(theta);
|
||||
var qy = cy + r * System.Math.Sin(theta);
|
||||
|
||||
var d = RayEdgeDistance(qx, qy, p1x, p1y, line.pt2.X, line.pt2.Y,
|
||||
dirX, dirY);
|
||||
if (d < minDist) { minDist = d; if (d <= 0) return 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,20 +126,10 @@ namespace OpenNest
|
||||
{
|
||||
var result = new List<Entity>(source.Count);
|
||||
|
||||
for (var i = 0; i < source.Count; i++)
|
||||
foreach (var entity in source)
|
||||
{
|
||||
var entity = source[i];
|
||||
Entity copy;
|
||||
|
||||
if (entity is Line line)
|
||||
copy = new Line(line.StartPoint + location, line.EndPoint + location);
|
||||
else if (entity is Arc arc)
|
||||
copy = new Arc(arc.Center + location, arc.Radius, arc.StartAngle, arc.EndAngle, arc.IsReversed);
|
||||
else if (entity is Circle circle)
|
||||
copy = new Circle(circle.Center + location, circle.Radius);
|
||||
else
|
||||
continue;
|
||||
|
||||
var copy = entity.Clone();
|
||||
copy.Offset(location);
|
||||
result.Add(copy);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace OpenNest.IO.Bending
|
||||
{
|
||||
return document.Entities
|
||||
.OfType<ACadSharp.Entities.Line>()
|
||||
.Where(l => l.Layer?.Name == "BEND"
|
||||
.Where(l => (l.Layer?.Name == "BEND" || l.Layer?.Name == "0")
|
||||
&& (l.LineType?.Name?.Contains("CENTER") == true
|
||||
|| l.LineType?.Name == "CENTERX2"))
|
||||
.ToList();
|
||||
|
||||
@@ -8,6 +8,76 @@ namespace OpenNest.Tests.Fill
|
||||
{
|
||||
public class CompactorTests
|
||||
{
|
||||
[Fact]
|
||||
public void DirectionalDistance_ArcVsInclinedLine_DoesNotOverPush()
|
||||
{
|
||||
// Arc (top semicircle) pushed upward toward a 45° inclined line.
|
||||
// The critical angle on the arc gives a shorter distance than any
|
||||
// sampled vertex (endpoints + cardinal extremes).
|
||||
var arc = new Arc(5, 0, 2, 0, System.Math.PI);
|
||||
var line = new Line(new Vector(3, 4), new Vector(7, 6));
|
||||
|
||||
var moving = new List<Entity> { arc };
|
||||
var stationary = new List<Entity> { line };
|
||||
var direction = new Vector(0, 1); // push up
|
||||
|
||||
var dist = SpatialQuery.DirectionalDistance(moving, stationary, direction);
|
||||
|
||||
// Move the arc up by the computed distance, then verify no overlap.
|
||||
// The topmost reachable point on the arc at the critical angle θ ≈ 2.034
|
||||
// (between π/2 and π) should just touch the line.
|
||||
Assert.True(dist < double.MaxValue, "Should find a finite distance");
|
||||
Assert.True(dist > 0, "Should be a positive distance");
|
||||
|
||||
// Verify: after moving, the closest point on the arc should be within
|
||||
// tolerance of the line, not past it.
|
||||
var theta = System.Math.Atan2(
|
||||
line.pt2.X - line.pt1.X, -(line.pt2.Y - line.pt1.Y));
|
||||
theta = OpenNest.Math.Angle.NormalizeRad(theta + System.Math.PI);
|
||||
var qx = arc.Center.X + arc.Radius * System.Math.Cos(theta);
|
||||
var qy = arc.Center.Y + arc.Radius * System.Math.Sin(theta) + dist;
|
||||
|
||||
// The moved point should be on or just touching the line, not past it.
|
||||
// Line equation: (y - 4) / (x - 3) = (6 - 4) / (7 - 3) = 0.5
|
||||
// y = 0.5x + 2.5
|
||||
var lineYAtQx = 0.5 * qx + 2.5;
|
||||
Assert.True(qy <= lineYAtQx + 0.001,
|
||||
$"Arc point ({qx:F4}, {qy:F4}) should not be past line (line Y={lineYAtQx:F4} at X={qx:F4}). " +
|
||||
$"dist={dist:F6}, overshot by {qy - lineYAtQx:F6}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectionalDistance_ArcVsInclinedLine_BetterThanVertexSampling()
|
||||
{
|
||||
// Same geometry — verify the analytical Phase 3 finds a shorter
|
||||
// distance than the Phase 1/2 vertex sampling alone would.
|
||||
var arc = new Arc(5, 0, 2, 0, System.Math.PI);
|
||||
var line = new Line(new Vector(3, 4), new Vector(7, 6));
|
||||
|
||||
// Phase 1/2 vertex-only distance: sample arc endpoints + cardinal extreme.
|
||||
var vertices = new[]
|
||||
{
|
||||
new Vector(7, 0), // arc endpoint θ=0
|
||||
new Vector(3, 0), // arc endpoint θ=π
|
||||
new Vector(5, 2), // cardinal extreme θ=π/2
|
||||
};
|
||||
|
||||
var vertexMin = double.MaxValue;
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
var d = SpatialQuery.RayEdgeDistance(v.X, v.Y,
|
||||
line.pt1.X, line.pt1.Y, line.pt2.X, line.pt2.Y, 0, 1);
|
||||
if (d < vertexMin) vertexMin = d;
|
||||
}
|
||||
|
||||
// Full directional distance (includes Phase 3 arc-to-line).
|
||||
var moving = new List<Entity> { arc };
|
||||
var stationary = new List<Entity> { line };
|
||||
var fullDist = SpatialQuery.DirectionalDistance(moving, stationary, new Vector(0, 1));
|
||||
|
||||
Assert.True(fullDist < vertexMin,
|
||||
$"Full distance ({fullDist:F6}) should be less than vertex-only ({vertexMin:F6})");
|
||||
}
|
||||
private static Drawing MakeRectDrawing(double w, double h)
|
||||
{
|
||||
var pgm = new OpenNest.CNC.Program();
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace OpenNest.Controls
|
||||
public event EventHandler FilterChanged;
|
||||
public event EventHandler<int> BendLineSelected;
|
||||
public event EventHandler<int> BendLineRemoved;
|
||||
public event EventHandler<int> BendLineEdited;
|
||||
public event EventHandler AddBendLineClicked;
|
||||
|
||||
public FilterPanel()
|
||||
@@ -51,6 +52,18 @@ namespace OpenNest.Controls
|
||||
bendLinesList.SelectedIndexChanged += (s, e) =>
|
||||
BendLineSelected?.Invoke(this, bendLinesList.SelectedIndex);
|
||||
|
||||
var bendEditLink = new LinkLabel
|
||||
{
|
||||
Text = "Edit",
|
||||
AutoSize = true,
|
||||
Font = new Font("Segoe UI", 8f)
|
||||
};
|
||||
bendEditLink.LinkClicked += (s, e) =>
|
||||
{
|
||||
if (bendLinesList.SelectedIndex >= 0)
|
||||
BendLineEdited?.Invoke(this, bendLinesList.SelectedIndex);
|
||||
};
|
||||
|
||||
var bendDeleteLink = new LinkLabel
|
||||
{
|
||||
Text = "Remove",
|
||||
@@ -63,6 +76,12 @@ namespace OpenNest.Controls
|
||||
BendLineRemoved?.Invoke(this, bendLinesList.SelectedIndex);
|
||||
};
|
||||
|
||||
bendLinesList.DoubleClick += (s, e) =>
|
||||
{
|
||||
if (bendLinesList.SelectedIndex >= 0)
|
||||
BendLineEdited?.Invoke(this, bendLinesList.SelectedIndex);
|
||||
};
|
||||
|
||||
bendAddLink = new LinkLabel
|
||||
{
|
||||
Text = "Add Bend Line",
|
||||
@@ -80,6 +99,7 @@ namespace OpenNest.Controls
|
||||
WrapContents = false
|
||||
};
|
||||
bendLinksPanel.Controls.Add(bendAddLink);
|
||||
bendLinksPanel.Controls.Add(bendEditLink);
|
||||
bendLinksPanel.Controls.Add(bendDeleteLink);
|
||||
|
||||
bendLinesPanel.ContentPanel.Controls.Add(bendLinesList);
|
||||
|
||||
@@ -209,15 +209,8 @@ namespace OpenNest.Controls
|
||||
|
||||
private static Entity CloneEntity(Entity entity, Color color)
|
||||
{
|
||||
Entity clone = entity switch
|
||||
{
|
||||
Line line => new Line(line.StartPoint, line.EndPoint) { Layer = line.Layer, IsVisible = line.IsVisible },
|
||||
Arc arc => new Arc(arc.Center, arc.Radius, arc.StartAngle, arc.EndAngle, arc.IsReversed) { Layer = arc.Layer, IsVisible = arc.IsVisible },
|
||||
Circle circle => new Circle(circle.Center, circle.Radius) { Layer = circle.Layer, IsVisible = circle.IsVisible },
|
||||
_ => null,
|
||||
};
|
||||
if (clone != null)
|
||||
clone.Color = color;
|
||||
var clone = entity.Clone();
|
||||
clone.Color = color;
|
||||
return clone;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,5 +99,17 @@ namespace OpenNest.Forms
|
||||
public double BendAngle => (double)numAngle.Value;
|
||||
|
||||
public double? BendRadius => chkRadius.Checked ? (double)numRadius.Value : null;
|
||||
|
||||
public void LoadBend(Bend bend)
|
||||
{
|
||||
cboDirection.SelectedIndex = bend.Direction == BendDirection.Up ? 1 : 0;
|
||||
if (bend.Angle.HasValue)
|
||||
numAngle.Value = (decimal)bend.Angle.Value;
|
||||
if (bend.Radius.HasValue)
|
||||
{
|
||||
chkRadius.Checked = true;
|
||||
numRadius.Value = (decimal)bend.Radius.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace OpenNest.Forms
|
||||
filterPanel.FilterChanged += OnFilterChanged;
|
||||
filterPanel.BendLineSelected += OnBendLineSelected;
|
||||
filterPanel.BendLineRemoved += OnBendLineRemoved;
|
||||
filterPanel.BendLineEdited += OnBendLineEdited;
|
||||
filterPanel.AddBendLineClicked += OnAddBendLineClicked;
|
||||
entityView1.LinePicked += OnLinePicked;
|
||||
entityView1.PickCancelled += OnPickCancelled;
|
||||
@@ -292,6 +293,29 @@ namespace OpenNest.Forms
|
||||
entityView1.Invalidate();
|
||||
}
|
||||
|
||||
private void OnBendLineEdited(object sender, int index)
|
||||
{
|
||||
var item = CurrentItem;
|
||||
if (item == null || index < 0 || index >= item.Bends.Count) return;
|
||||
|
||||
var bend = item.Bends[index];
|
||||
using var dialog = new BendLineDialog();
|
||||
dialog.LoadBend(bend);
|
||||
|
||||
if (dialog.ShowDialog(this) != DialogResult.OK) return;
|
||||
|
||||
bend.Direction = dialog.Direction;
|
||||
bend.Angle = dialog.BendAngle;
|
||||
bend.Radius = dialog.BendRadius;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void OnQuantityChanged(object sender, EventArgs e)
|
||||
{
|
||||
var item = CurrentItem;
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
A Windows desktop application for CNC nesting — imports DXF drawings, arranges parts on material plates, and exports layouts as DXF or G-code for cutting.
|
||||
|
||||

|
||||
<p>
|
||||
<a href="screenshots/screenshot-nest-1.png"><img src="screenshots/screenshot-nest-1.png" width="420" alt="OpenNest - parts nested on a 36x36 plate"></a>
|
||||
<a href="screenshots/screenshot-nest-2.png"><img src="screenshots/screenshot-nest-2.png" width="420" alt="OpenNest - 44 parts nested on a 60x120 plate"></a>
|
||||
</p>
|
||||
|
||||
OpenNest takes your part drawings, lets you define your sheet (plate) sizes, and arranges the parts to make efficient use of material. The result can be exported as DXF files or post-processed into G-code that your CNC cutting machine understands.
|
||||
|
||||
@@ -46,8 +49,6 @@ OpenNest takes your part drawings, lets you define your sheet (plate) sizes, and
|
||||
| **User-Defined Variables** | Named G-code variables (`$name`) emitted as machine variables (`#200+`) at post time |
|
||||
| **Post-Processors** | Plugin-based G-code generation; Cincinnati CL-707/800/900/940/CLX included |
|
||||
|
||||

|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Windows 10 or later**
|
||||
@@ -80,6 +81,15 @@ Or open `OpenNest.sln` in Visual Studio and run the `OpenNest` project.
|
||||
5. **Add cut-offs** — Optionally add horizontal/vertical cut-off lines to trim unused plate material
|
||||
6. **Export** — Save as a `.nest` file, export to DXF, or post-process to G-code
|
||||
|
||||
### CAD Converter
|
||||
|
||||
The CAD Converter turns DXF/DWG files into nest-ready drawings. Toggle layers, colors, and linetypes to exclude construction geometry; review detected bend lines; and preview the generated cut program with contour ordering before accepting the drawing into the nest.
|
||||
|
||||
<p>
|
||||
<a href="screenshots/screenshot-cad-converter-1.png"><img src="screenshots/screenshot-cad-converter-1.png" width="420" alt="CAD Converter — layer, color, and linetype filtering"></a>
|
||||
<a href="screenshots/screenshot-cad-converter-2.png"><img src="screenshots/screenshot-cad-converter-2.png" width="420" alt="CAD Converter — contour list and G-code preview"></a>
|
||||
</p>
|
||||
|
||||
## Command-Line Interface
|
||||
|
||||
OpenNest includes a CLI for batch nesting without the GUI — useful for automation, scripting, and CI pipelines.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
Reference in New Issue
Block a user