feat(perf): direct STL parsing for near-instant large-file load (v0.7.1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XgHf5MW39U3Xo8y2uA229K
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SmokeTest;
|
||||
|
||||
/// <summary>產生測試用 binary STL:一片 gridN×gridN 的網格,每格 2 個三角形(波浪起伏,避免全共面)。</summary>
|
||||
internal static class MakeStl
|
||||
{
|
||||
public static void Run(string path, int triCount)
|
||||
{
|
||||
// 每格 2 三角,格數 = triCount/2;邊長 grid = ceil(sqrt(cells))
|
||||
int cells = System.Math.Max(1, triCount / 2);
|
||||
int grid = (int)System.Math.Ceiling(System.Math.Sqrt(cells));
|
||||
int tris = grid * grid * 2;
|
||||
|
||||
using var fs = File.Create(path);
|
||||
using var bw = new BinaryWriter(fs);
|
||||
bw.Write(new byte[80]); // header
|
||||
bw.Write((uint)tris); // triangle count
|
||||
|
||||
static void WriteTri(BinaryWriter w, float ax, float ay, float az,
|
||||
float bx, float by, float bz, float cx, float cy, float cz)
|
||||
{
|
||||
for (int i = 0; i < 3; i++) w.Write(0f); // normal (0,0,0)
|
||||
w.Write(ax); w.Write(ay); w.Write(az);
|
||||
w.Write(bx); w.Write(by); w.Write(bz);
|
||||
w.Write(cx); w.Write(cy); w.Write(cz);
|
||||
w.Write((ushort)0); // attribute byte count
|
||||
}
|
||||
|
||||
float Z(int x, int y) => (float)(System.Math.Sin(x * 0.1) * System.Math.Cos(y * 0.1) * 5.0);
|
||||
for (int y = 0; y < grid; y++)
|
||||
for (int x = 0; x < grid; x++)
|
||||
{
|
||||
float x0 = x, x1 = x + 1, y0 = y, y1 = y + 1;
|
||||
WriteTri(bw, x0, y0, Z(x, y), x1, y0, Z(x + 1, y), x1, y1, Z(x + 1, y + 1));
|
||||
WriteTri(bw, x0, y0, Z(x, y), x1, y1, Z(x + 1, y + 1), x0, y1, Z(x, y + 1));
|
||||
}
|
||||
|
||||
Console.WriteLine($"寫出 {tris:N0} 三角形 → {path} ({new FileInfo(path).Length:N0} bytes)");
|
||||
}
|
||||
}
|
||||
+86
-80
@@ -1,80 +1,86 @@
|
||||
// 無 UI smoke test:驗證 CAD 檔解析 → 三角化 → 裝配樹 整條匯入管線
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using STPViewer.Services;
|
||||
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("用法: SmokeTest [--tree] <file.stp|.stl|.dxf> [...]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (args[0] == "--tree")
|
||||
{
|
||||
foreach (string p in args.Skip(1)) SmokeTest.TreeDump.Run(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args[0] == "--make-dxf")
|
||||
{
|
||||
SmokeTest.MakeDxf.Run(args[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args[0] == "--clip-test")
|
||||
return SmokeTest.ClipTest.Run() == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--interference-test")
|
||||
return SmokeTest.InterferenceTest.Run() == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--align-test")
|
||||
return SmokeTest.AlignTest.Run() == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--bbox")
|
||||
return SmokeTest.BBoxDump.Run(args[1]);
|
||||
|
||||
if (args[0] == "--export-test")
|
||||
return SmokeTest.ExportTest.Run(args[1], args[2]) == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--stl-export-test")
|
||||
return SmokeTest.StlExportTest.Run(args.Length > 1 ? args[1] : null) == 0 ? 0 : 2;
|
||||
|
||||
var service = new StepImportService { Progress = s => Console.WriteLine($" [階段] {s}") };
|
||||
int failed = 0;
|
||||
|
||||
foreach (string path in args)
|
||||
{
|
||||
Console.WriteLine($"=== {Path.GetFileName(path)} ===");
|
||||
var sw = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
ImportedFileData data = service.Import(path);
|
||||
sw.Stop();
|
||||
ImportedNode r = data.Root;
|
||||
Console.WriteLine($" Solids : {r.SolidCount}");
|
||||
Console.WriteLine($" Faces : {r.FaceCount}");
|
||||
Console.WriteLine($" Triangles : {r.TriangleCount:N0}");
|
||||
Console.WriteLine($" Bounds : {r.Bounds.SizeX:F2} x {r.Bounds.SizeY:F2} x {r.Bounds.SizeZ:F2} mm");
|
||||
Console.WriteLine($" Time : {sw.ElapsedMilliseconds} ms");
|
||||
Console.WriteLine(" --- 裝配樹 ---");
|
||||
PrintNode(r, 1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sw.Stop();
|
||||
failed++;
|
||||
Console.WriteLine($" FAILED ({sw.ElapsedMilliseconds} ms): {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return failed == 0 ? 0 : 2;
|
||||
|
||||
static void PrintNode(ImportedNode n, int indent)
|
||||
{
|
||||
string seg = n.EdgeSegments is null ? "" : $", edgeSegs={n.EdgeSegments.Count / 2:N0}";
|
||||
Console.WriteLine($"{new string(' ', indent * 2)}{n.Name} " +
|
||||
$"[solids={n.SolidCount}, faces={n.FaceCount}, tris={n.TriangleCount:N0}{seg}{(n.HasBrep ? "" : ", 無B-rep")}]");
|
||||
foreach (ImportedNode c in n.Children) PrintNode(c, indent + 1);
|
||||
}
|
||||
// 無 UI smoke test:驗證 CAD 檔解析 → 三角化 → 裝配樹 整條匯入管線
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using STPViewer.Services;
|
||||
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("用法: SmokeTest [--tree] <file.stp|.stl|.dxf> [...]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (args[0] == "--tree")
|
||||
{
|
||||
foreach (string p in args.Skip(1)) SmokeTest.TreeDump.Run(p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args[0] == "--make-dxf")
|
||||
{
|
||||
SmokeTest.MakeDxf.Run(args[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args[0] == "--make-stl")
|
||||
{
|
||||
SmokeTest.MakeStl.Run(args[1], args.Length > 2 ? int.Parse(args[2]) : 1_500_000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (args[0] == "--clip-test")
|
||||
return SmokeTest.ClipTest.Run() == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--interference-test")
|
||||
return SmokeTest.InterferenceTest.Run() == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--align-test")
|
||||
return SmokeTest.AlignTest.Run() == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--bbox")
|
||||
return SmokeTest.BBoxDump.Run(args[1]);
|
||||
|
||||
if (args[0] == "--export-test")
|
||||
return SmokeTest.ExportTest.Run(args[1], args[2]) == 0 ? 0 : 2;
|
||||
|
||||
if (args[0] == "--stl-export-test")
|
||||
return SmokeTest.StlExportTest.Run(args.Length > 1 ? args[1] : null) == 0 ? 0 : 2;
|
||||
|
||||
var service = new StepImportService { Progress = s => Console.WriteLine($" [階段] {s}") };
|
||||
int failed = 0;
|
||||
|
||||
foreach (string path in args)
|
||||
{
|
||||
Console.WriteLine($"=== {Path.GetFileName(path)} ===");
|
||||
var sw = Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
ImportedFileData data = service.Import(path);
|
||||
sw.Stop();
|
||||
ImportedNode r = data.Root;
|
||||
Console.WriteLine($" Solids : {r.SolidCount}");
|
||||
Console.WriteLine($" Faces : {r.FaceCount}");
|
||||
Console.WriteLine($" Triangles : {r.TriangleCount:N0}");
|
||||
Console.WriteLine($" Bounds : {r.Bounds.SizeX:F2} x {r.Bounds.SizeY:F2} x {r.Bounds.SizeZ:F2} mm");
|
||||
Console.WriteLine($" Time : {sw.ElapsedMilliseconds} ms");
|
||||
Console.WriteLine(" --- 裝配樹 ---");
|
||||
PrintNode(r, 1);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sw.Stop();
|
||||
failed++;
|
||||
Console.WriteLine($" FAILED ({sw.ElapsedMilliseconds} ms): {ex.GetType().Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return failed == 0 ? 0 : 2;
|
||||
|
||||
static void PrintNode(ImportedNode n, int indent)
|
||||
{
|
||||
string seg = n.EdgeSegments is null ? "" : $", edgeSegs={n.EdgeSegments.Count / 2:N0}";
|
||||
Console.WriteLine($"{new string(' ', indent * 2)}{n.Name} " +
|
||||
$"[solids={n.SolidCount}, faces={n.FaceCount}, tris={n.TriangleCount:N0}{seg}{(n.HasBrep ? "" : ", 無B-rep")}]");
|
||||
foreach (ImportedNode c in n.Children) PrintNode(c, indent + 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user