feat: improve DXF export and cross-platform file handling

- PepSettings: add DrawingsDirectory property for DXF endpoint config
- DrawingDxfExporter: support subprogram calls, skip non-cut linear moves,
  remove spurious radians-to-degrees conversion on arc angles, set AC1018 DXF version
- DrawingReader: handle .loop-info/.cadsnapshot entries gracefully, derive
  DrawingInfo from loop names when .info entry is absent, use FileShare.Read
- DrawingInfoReader: use TryParse instead of Parse for material number
- Drawing: make GetLoopName public (used by DrawingDxfExporter)
- ZipHelper: use FileShare.Read to allow concurrent readers on Windows/Linux
- NestFilterData: skip status filter when value is "All"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
aj
2026-06-27 07:23:12 -04:00
co-authored by Claude Sonnet 4.6
parent 5e0856b5d1
commit 85f6a50f1a
7 changed files with 139 additions and 82 deletions
+1
View File
@@ -4,4 +4,5 @@ public class PepSettings
{ {
public string NestDirectory { get; set; } = string.Empty; public string NestDirectory { get; set; } = string.Empty;
public string MaterialsFile { get; set; } = string.Empty; public string MaterialsFile { get; set; } = string.Empty;
public string DrawingsDirectory { get; set; } = string.Empty;
} }
+1 -1
View File
@@ -77,7 +77,7 @@ namespace PepApi.Core.Models
nests = nests.Where(n => n.DateCreated <= this.EndDate); nests = nests.Where(n => n.DateCreated <= this.EndDate);
} }
if (this.Status != null) if (this.Status != null && !this.Status.Equals("All", StringComparison.OrdinalIgnoreCase))
{ {
var x = this.Status.ToUpper(); var x = this.Status.ToUpper();
nests = nests.Where(n => n.Status.ToUpper() == x); nests = nests.Where(n => n.Status.ToUpper() == x);
+57 -16
View File
@@ -1,4 +1,4 @@
using ACadSharp; using ACadSharp;
using ACadSharp.IO; using ACadSharp.IO;
using ACadSharp.Tables; using ACadSharp.Tables;
using CSMath; using CSMath;
@@ -17,13 +17,43 @@ public static class DrawingDxfExporter
public static Stream Export(Drawing drawing) public static Stream Export(Drawing drawing)
{ {
var doc = new CadDocument(); var doc = new CadDocument();
doc.Header.Version = ACadVersion.AC1018;
// Collect loop IDs that are called as subprograms so we skip drawing them as top-level
var subprogramIds = new HashSet<int>();
foreach (var loop in drawing.Loops)
{
foreach (var code in loop)
{
if (code.CodeType() == CodeType.SubProgramCall)
subprogramIds.Add(((SubProgramCall)code).LoopId);
}
}
foreach (var loop in drawing.Loops) 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); var layer = new Layer(loop.Name);
doc.Layers.Add(layer); doc.Layers.Add(layer);
var pos = new Vector(); DrawLoop(doc, drawing, loop, layer, new Vector());
}
var buffer = new MemoryStream();
using (var writer = new DxfWriter(buffer, doc, false))
{
writer.Write();
}
return new MemoryStream(buffer.ToArray());
}
private static Vector DrawLoop(CadDocument doc, Drawing drawing, Loop loop, Layer layer, Vector startPos)
{
var pos = startPos;
foreach (var code in loop) foreach (var code in loop)
{ {
@@ -35,6 +65,8 @@ public static class DrawingDxfExporter
case CodeType.LinearMove: case CodeType.LinearMove:
var lm = (LinearMove)code; var lm = (LinearMove)code;
if (lm.Type == EntityType.Cut)
{
var lineEnd = Advance(pos, lm.EndPoint, loop.Mode); var lineEnd = Advance(pos, lm.EndPoint, loop.Mode);
doc.Entities.Add(new AcadLine doc.Entities.Add(new AcadLine
{ {
@@ -43,6 +75,11 @@ public static class DrawingDxfExporter
Layer = layer Layer = layer
}); });
pos = lineEnd; pos = lineEnd;
}
else
{
pos = Advance(pos, lm.EndPoint, loop.Mode);
}
break; break;
case CodeType.CircularMove: case CodeType.CircularMove:
@@ -58,11 +95,9 @@ public static class DrawingDxfExporter
var endAngle = Math.Atan2(arcEnd.Y - arcCenter.Y, arcEnd.X - arcCenter.X); var endAngle = Math.Atan2(arcEnd.Y - arcCenter.Y, arcEnd.X - arcCenter.X);
if (cm.Rotation == RotationType.CW) if (cm.Rotation == RotationType.CW)
{
(startAngle, endAngle) = (endAngle, startAngle); (startAngle, endAngle) = (endAngle, startAngle);
}
if (Math.Abs(startAngle - endAngle) < 1e-10) // full circle if (Math.Abs(startAngle - endAngle) < 1e-10)
{ {
doc.Entities.Add(new AcadCircle doc.Entities.Add(new AcadCircle
{ {
@@ -77,26 +112,33 @@ public static class DrawingDxfExporter
{ {
Center = ToXYZ(arcCenter), Center = ToXYZ(arcCenter),
Radius = radius, Radius = radius,
StartAngle = startAngle * (180.0 / Math.PI), StartAngle = startAngle,
EndAngle = endAngle * (180.0 / Math.PI), EndAngle = endAngle,
Layer = layer Layer = layer
}); });
} }
pos = arcEnd; pos = arcEnd;
break; break;
}
case CodeType.SubProgramCall:
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);
// pos unchanged — subprograms are closed shapes that return to start
break;
} }
} }
var buffer = new MemoryStream(); return pos;
using (var writer = new DxfWriter(buffer, doc, false)) }
private static int GetLoopNumber(string loopName)
{ {
writer.Write(); var idx = loopName.LastIndexOf(".loop-", StringComparison.OrdinalIgnoreCase);
} if (idx < 0) return -1;
return int.TryParse(loopName.Substring(idx + 6), out var n) ? n : -1;
var stream = new MemoryStream(buffer.ToArray());
return stream;
} }
private static Vector Advance(Vector current, Vector offset, ProgrammingMode mode) => private static Vector Advance(Vector current, Vector offset, ProgrammingMode mode) =>
@@ -104,4 +146,3 @@ public static class DrawingDxfExporter
private static XYZ ToXYZ(Vector v) => new XYZ(v.X, v.Y, 0); private static XYZ ToXYZ(Vector v) => new XYZ(v.X, v.Y, 0);
} }
+2 -1
View File
@@ -34,7 +34,8 @@ namespace PepLib.IO
stream.Seek(0x9, SeekOrigin.Current); stream.Seek(0x9, SeekOrigin.Current);
Info.MaterialNumber = int.Parse(ReadString(0x40, ref stream)); int.TryParse(ReadString(0x40, ref stream), out var materialNumber);
Info.MaterialNumber = materialNumber;
Info.MaterialGrade = ReadString(0x10, ref stream); Info.MaterialGrade = ReadString(0x10, ref stream);
Info.ProgrammedBy = ReadString(0x40, ref stream); Info.ProgrammedBy = ReadString(0x40, ref stream);
Info.CreatedBy = ReadString(0x40, ref stream); Info.CreatedBy = ReadString(0x40, ref stream);
+21 -7
View File
@@ -1,5 +1,4 @@
using PepLib.Models; using PepLib.Models;
using System.Diagnostics;
using System.IO.Compression; using System.IO.Compression;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -38,6 +37,11 @@ namespace PepLib.IO
LoadInfo(memstream); LoadInfo(memstream);
memstream.Close(); memstream.Close();
continue; continue;
case ".loop-info":
case ".cadsnapshot":
memstream.Close();
continue;
} }
if (Regex.IsMatch(extension, "loop-\\d\\d\\d")) if (Regex.IsMatch(extension, "loop-\\d\\d\\d"))
@@ -47,6 +51,9 @@ namespace PepLib.IO
} }
} }
if (Drawing.Info == null)
Drawing.Info = DeriveInfoFromLoops();
Drawing.ResolveLoops(); Drawing.ResolveLoops();
} }
@@ -62,7 +69,7 @@ namespace PepLib.IO
try try
{ {
stream = new FileStream(nestFile, FileMode.Open); stream = new FileStream(nestFile, FileMode.Open, FileAccess.Read, FileShare.Read);
Read(stream); Read(stream);
} }
finally finally
@@ -73,16 +80,23 @@ namespace PepLib.IO
} }
private void LoadInfo(Stream stream) private void LoadInfo(Stream stream)
{
try
{ {
Drawing.Info = DrawingInfo.Load(stream); Drawing.Info = DrawingInfo.Load(stream);
} }
catch (Exception exception)
private DrawingInfo DeriveInfoFromLoops()
{ {
Debug.WriteLine(exception.Message); var info = new DrawingInfo();
Debug.WriteLine(exception.StackTrace);
if (Drawing.Loops.Count > 0)
{
// Loop names follow the pattern "{DrawingName}.loop-XXX"
var loopName = Drawing.Loops[0].Name;
var suffix = loopName.LastIndexOf(".loop-", StringComparison.OrdinalIgnoreCase);
info.Name = suffix >= 0 ? loopName.Substring(0, suffix) : loopName;
} }
return info;
} }
private Loop ReadLoop(string name, Stream stream) private Loop ReadLoop(string name, Stream stream)
+1 -1
View File
@@ -76,7 +76,7 @@ namespace PepLib.Models
return null; return null;
} }
private string GetLoopName(int loopId) public string GetLoopName(int loopId)
{ {
return string.Format("{0}.loop-{1}", Info.Name, loopId.ToString().PadLeft(3, '0')); return string.Format("{0}.loop-{1}", Info.Name, loopId.ToString().PadLeft(3, '0'));
} }
+2 -2
View File
@@ -18,7 +18,7 @@ namespace PepLib.Utilities
var nameList = new List<string>(); var nameList = new List<string>();
var streamList = new List<Stream>(); var streamList = new List<Stream>();
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var zip = new ZipArchive(fileStream, ZipArchiveMode.Read)) using (var zip = new ZipArchive(fileStream, ZipArchiveMode.Read))
{ {
foreach (var entry in zip.Entries) foreach (var entry in zip.Entries)
@@ -52,7 +52,7 @@ namespace PepLib.Utilities
/// <returns></returns> /// <returns></returns>
public static bool ExtractByExtension(string file, string extension, out string name, out Stream stream) public static bool ExtractByExtension(string file, string extension, out string name, out Stream stream)
{ {
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var zip = new ZipArchive(fileStream, ZipArchiveMode.Read)) using (var zip = new ZipArchive(fileStream, ZipArchiveMode.Read))
{ {
foreach (var entry in zip.Entries) foreach (var entry in zip.Entries)