feat: separate DXF entities onto per-type layers, recognize SCRIBE marker
Build PepApi image / build-and-push (push) Successful in 45s
Build PepApi image / build-and-push (push) Successful in 45s
DrawLoop previously drew every entity onto the single loop layer, so scribe/display marks were indistinguishable from cuts in the exported DXF. Non-cut entity types now get their own layer, and ProgramReader now parses the SCRIBE marker into EntityType.Scribe so scribed moves route to their own layer instead of falling through as untyped cuts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,4 +64,62 @@ public class DrawingDxfExporterTests
|
||||
Assert.InRange(arc.StartAngle, 0, 360);
|
||||
Assert.InRange(arc.EndAngle, 0, 360);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Export_NonCutLinearMove_IsDrawnOnDedicatedLayerInsteadOfDropped()
|
||||
{
|
||||
var drawing = new Drawing();
|
||||
drawing.Info = new DrawingInfo { Name = "TEST" };
|
||||
var loop = new Loop { Name = "TEST.loop-000" };
|
||||
loop.Add(new RapidMove(new Vector(0, 0)));
|
||||
loop.Add(new LinearMove(new Vector(10, 0)) { Type = EntityType.InternalLeadin });
|
||||
drawing.Loops.Add(loop);
|
||||
|
||||
using var stream = DrawingDxfExporter.Export(drawing);
|
||||
|
||||
stream.Position = 0;
|
||||
using var reader = new ACadSharp.IO.DxfReader(stream);
|
||||
var doc = reader.Read();
|
||||
var line = doc.Entities.OfType<ACadSharp.Entities.Line>().Single();
|
||||
Assert.Equal("InternalLeadin", line.Layer.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Export_NonCutCircularMove_IsDrawnOnDedicatedLayerInsteadOfCutLayer()
|
||||
{
|
||||
var drawing = new Drawing();
|
||||
drawing.Info = new DrawingInfo { Name = "TEST" };
|
||||
var loop = new Loop { Name = "TEST.loop-000" };
|
||||
loop.Add(new RapidMove(new Vector(5, 0)));
|
||||
loop.Add(new CircularMove(new Vector(-5, 5), new Vector(-5, 0), RotationType.CCW) { Type = EntityType.ExternalLeadout });
|
||||
drawing.Loops.Add(loop);
|
||||
|
||||
using var stream = DrawingDxfExporter.Export(drawing);
|
||||
|
||||
stream.Position = 0;
|
||||
using var reader = new ACadSharp.IO.DxfReader(stream);
|
||||
var doc = reader.Read();
|
||||
var arc = doc.Entities.OfType<ACadSharp.Entities.Arc>().Single();
|
||||
Assert.Equal("ExternalLeadout", arc.Layer.Name);
|
||||
Assert.NotEqual("TEST.loop-000", arc.Layer.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Export_CutLinearMove_StaysOnLoopNamedLayer()
|
||||
{
|
||||
var drawing = new Drawing();
|
||||
drawing.Info = new DrawingInfo { Name = "TEST" };
|
||||
var loop = new Loop { Name = "TEST.loop-000" };
|
||||
loop.Add(new RapidMove(new Vector(0, 0)));
|
||||
loop.Add(new LinearMove(new Vector(10, 0)));
|
||||
drawing.Loops.Add(loop);
|
||||
|
||||
using var stream = DrawingDxfExporter.Export(drawing);
|
||||
|
||||
stream.Position = 0;
|
||||
using var reader = new ACadSharp.IO.DxfReader(stream);
|
||||
var doc = reader.Read();
|
||||
var line = doc.Entities.OfType<ACadSharp.Entities.Line>().Single();
|
||||
Assert.Equal("TEST.loop-000", line.Layer.Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Text;
|
||||
using PepLib.Codes;
|
||||
using PepLib.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace PepLib.Core.Tests.IO;
|
||||
|
||||
public class ProgramReaderTests
|
||||
{
|
||||
// ProgramReader reads fixed 200-byte ASCII records (no newlines), matching
|
||||
// the raw record format found inside real .pep loop entries.
|
||||
private const int RecordSize = 200;
|
||||
|
||||
private static Stream RecordStream(params string[] lines)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var line in lines)
|
||||
sb.Append(line.PadRight(RecordSize));
|
||||
|
||||
return new MemoryStream(Encoding.ASCII.GetBytes(sb.ToString()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_LinearMoveWithScribeMarker_SetsTypeToScribe()
|
||||
{
|
||||
using var stream = RecordStream("N50G1X1Y0:SCRIBE");
|
||||
|
||||
var program = Program.Load(stream);
|
||||
|
||||
var move = Assert.IsType<LinearMove>(Assert.Single(program));
|
||||
Assert.Equal(EntityType.Scribe, move.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CircularMoveWithScribeMarker_SetsTypeToScribe()
|
||||
{
|
||||
using var stream = RecordStream("N50G2X1Y0I0.5J0:SCRIBE");
|
||||
|
||||
var program = Program.Load(stream);
|
||||
|
||||
var move = Assert.IsType<CircularMove>(Assert.Single(program));
|
||||
Assert.Equal(EntityType.Scribe, move.Type);
|
||||
}
|
||||
}
|
||||
@@ -30,16 +30,17 @@ public static class DrawingDxfExporter
|
||||
}
|
||||
}
|
||||
|
||||
var layers = new Dictionary<string, Layer>();
|
||||
|
||||
foreach (var loop in drawing.Loops)
|
||||
{
|
||||
var loopNumber = GetLoopNumber(loop.Name);
|
||||
if (subprogramIds.Contains(loopNumber))
|
||||
continue; // drawn inline at SubProgramCall positions
|
||||
|
||||
var layer = new Layer(loop.Name);
|
||||
doc.Layers.Add(layer);
|
||||
var cutLayer = GetOrCreateLayer(doc, layers, loop.Name);
|
||||
|
||||
DrawLoop(doc, drawing, loop, layer, new Vector());
|
||||
DrawLoop(doc, drawing, loop, cutLayer, layers, new Vector());
|
||||
}
|
||||
|
||||
var buffer = new MemoryStream();
|
||||
@@ -51,7 +52,7 @@ public static class DrawingDxfExporter
|
||||
return new MemoryStream(buffer.ToArray());
|
||||
}
|
||||
|
||||
private static Vector DrawLoop(CadDocument doc, Drawing drawing, Loop loop, Layer layer, Vector startPos)
|
||||
private static Vector DrawLoop(CadDocument doc, Drawing drawing, Loop loop, Layer cutLayer, Dictionary<string, Layer> layers, Vector startPos)
|
||||
{
|
||||
var pos = startPos;
|
||||
|
||||
@@ -65,21 +66,14 @@ public static class DrawingDxfExporter
|
||||
|
||||
case CodeType.LinearMove:
|
||||
var lm = (LinearMove)code;
|
||||
if (lm.Type == EntityType.Cut)
|
||||
{
|
||||
var lineEnd = Advance(pos, lm.EndPoint, loop.Mode);
|
||||
doc.Entities.Add(new AcadLine
|
||||
{
|
||||
StartPoint = ToXYZ(pos),
|
||||
EndPoint = ToXYZ(lineEnd),
|
||||
Layer = layer
|
||||
Layer = LayerFor(doc, layers, cutLayer, lm.Type)
|
||||
});
|
||||
pos = lineEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = Advance(pos, lm.EndPoint, loop.Mode);
|
||||
}
|
||||
break;
|
||||
|
||||
case CodeType.CircularMove:
|
||||
@@ -97,13 +91,15 @@ public static class DrawingDxfExporter
|
||||
if (cm.Rotation == RotationType.CW)
|
||||
(startAngle, endAngle) = (endAngle, startAngle);
|
||||
|
||||
var arcLayer = LayerFor(doc, layers, cutLayer, cm.Type);
|
||||
|
||||
if (Math.Abs(startAngle - endAngle) < 1e-10)
|
||||
{
|
||||
doc.Entities.Add(new AcadCircle
|
||||
{
|
||||
Center = ToXYZ(arcCenter),
|
||||
Radius = radius,
|
||||
Layer = layer
|
||||
Layer = arcLayer
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -114,7 +110,7 @@ public static class DrawingDxfExporter
|
||||
Radius = radius,
|
||||
StartAngle = startAngle,
|
||||
EndAngle = endAngle,
|
||||
Layer = layer
|
||||
Layer = arcLayer
|
||||
});
|
||||
}
|
||||
|
||||
@@ -125,7 +121,7 @@ public static class DrawingDxfExporter
|
||||
var call = (SubProgramCall)code;
|
||||
var subLoop = drawing.Loops.FirstOrDefault(l => l.Name == drawing.GetLoopName(call.LoopId));
|
||||
if (subLoop != null)
|
||||
DrawLoop(doc, drawing, subLoop, layer, pos);
|
||||
DrawLoop(doc, drawing, subLoop, cutLayer, layers, pos);
|
||||
// pos unchanged — subprograms are closed shapes that return to start
|
||||
break;
|
||||
}
|
||||
@@ -134,6 +130,20 @@ public static class DrawingDxfExporter
|
||||
return pos;
|
||||
}
|
||||
|
||||
private static Layer LayerFor(CadDocument doc, Dictionary<string, Layer> layers, Layer cutLayer, EntityType type) =>
|
||||
type == EntityType.Cut ? cutLayer : GetOrCreateLayer(doc, layers, type.ToString());
|
||||
|
||||
private static Layer GetOrCreateLayer(CadDocument doc, Dictionary<string, Layer> layers, string name)
|
||||
{
|
||||
if (layers.TryGetValue(name, out var existing))
|
||||
return existing;
|
||||
|
||||
var layer = new Layer(name);
|
||||
doc.Layers.Add(layer);
|
||||
layers[name] = layer;
|
||||
return layer;
|
||||
}
|
||||
|
||||
private static int GetLoopNumber(string loopName)
|
||||
{
|
||||
var idx = loopName.LastIndexOf(".loop-", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -189,6 +189,10 @@ namespace PepLib.IO
|
||||
case "DISPLAY":
|
||||
type = EntityType.Display;
|
||||
break;
|
||||
|
||||
case "SCRIBE":
|
||||
type = EntityType.Scribe;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -266,6 +270,10 @@ namespace PepLib.IO
|
||||
case "DISPLAY":
|
||||
type = EntityType.Display;
|
||||
break;
|
||||
|
||||
case "SCRIBE":
|
||||
type = EntityType.Scribe;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user