docs: restore Chinese design-rationale comments after i18n merge

PR #1 condensed the existing Chinese comments into shorter English,
dropping the 'why / don't revert to X' notes the project relies on
(merged-mesh face lookup, Identity-not-null transforms, background
section clip, ...). Restore the original comments verbatim where the
code is unchanged; new i18n code keeps its English comments. Two
FacesContent/MergedContent notes are corrected to the current render
modes. Comment-only change (verified: comment-stripped code identical).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 09:48:56 +08:00
co-authored by Claude Opus 5.5
parent 6d48d7f852
commit c65fc4e67f
39 changed files with 480 additions and 410 deletions
+8 -7
View File
@@ -1,4 +1,5 @@
// Three-point alignment checks: known transform, Matrix3D/ModOp agreement, and collinear rejection.
// 三點對齊剛體變換數學驗證:
// (1) 已知變換還原 (2) Matrix3D ↔ ModOp 兩種表示一致 (3) 共線拒絕
using System.Windows.Media.Media3D;
using STPViewer.Services;
@@ -10,29 +11,29 @@ internal static class AlignTest
{
int failures = 0;
// Recover a known 90-degree Z rotation followed by translation (5,-3,2).
// ── (1) 已知剛體變換:繞 Z 軸 90° + 平移 (5,-3,2),三點求解後應還原同一變換 ──
var truth = Matrix3D.Identity;
truth.RotateAt(new Quaternion(new Vector3D(0, 0, 1), 90), new Point3D(2, 1, 0));
truth.Translate(new Vector3D(5, -3, 2));
var p1 = new Point3D(0, 0, 0);
var p2 = new Point3D(10, 0, 0);
var p3 = new Point3D(0, 7, 3); // Not collinear with the first two points.
var p3 = new Point3D(0, 7, 3); // 不共線、不在同平面軸上
Point3D q1 = truth.Transform(p1), q2 = truth.Transform(p2), q3 = truth.Transform(p3);
bool ok = RigidAlign.TryRigidTransform(p1, p2, p3, q1, q2, q3, out Matrix3D m);
failures += Check("three-point transform solved", ok, true);
// Corresponding points must coincide.
// 三個對應點要精確貼合
failures += CheckPoint("p1→q1", m.Transform(p1), q1);
failures += CheckPoint("p2→q2", m.Transform(p2), q2);
failures += CheckPoint("p3→q3", m.Transform(p3), q3);
// An independent fourth point checks the full rigid transform.
// 剛體變換唯一性:任意第 4 點也應該跟著正確變換
var r = new Point3D(-4, 12, 8);
failures += CheckPoint("fourth point", m.Transform(r), truth.Transform(r));
// CADability ModOp and WPF Matrix3D must transform points identically.
// ── (2) ToModOp:CADability ModOp 與 WPF Matrix3D 對同一點要算出同樣結果 ──
CADability.ModOp op = RigidAlign.ToModOp(m);
foreach (Point3D p in new[] { p1, p2, p3, r, new Point3D(1.5, -2.25, 9) })
{
@@ -47,7 +48,7 @@ internal static class AlignTest
}
System.Console.WriteLine($" {(failures == 0 ? "PASS" : "----")}: ModOp/Matrix3D agree at five points");
// Collinear source points must be rejected.
// ── (3) 共線拒絕 ──
bool collinearRejected = !RigidAlign.TryRigidTransform(
new Point3D(0, 0, 0), new Point3D(1, 0, 0), new Point3D(2, 0, 0), // Collinear.
q1, q2, q3, out _);
+7 -7
View File
@@ -1,4 +1,4 @@
// Section clipping checks the retained side and triangle area.
// 剖面裁切數學驗證:對單位幾何裁切,檢查保留側與面積守恆
using System.Windows.Media;
using System.Windows.Media.Media3D;
using STPViewer.Services;
@@ -11,7 +11,7 @@ internal static class ClipTest
{
int failures = 0;
// A single triangle with area 50.
// 單一三角形 (0,0,0)(10,0,0)(0,10,0),面積 50
var mesh = new MeshGeometry3D
{
Positions = new Point3DCollection
@@ -22,25 +22,25 @@ internal static class ClipTest
};
mesh.Freeze();
// A plane at x=20 retains the full triangle.
// case 1: 平面 x=20,全保留
var m1 = SectionService.ClipMesh(mesh, new Point3D(20, 0, 0), new Vector3D(1, 0, 0));
failures += Check("fully retained", Area(m1), 50.0);
// A plane at x=-1 removes the full triangle.
// case 2: 平面 x=-1,全裁掉
var m2 = SectionService.ClipMesh(mesh, new Point3D(-1, 0, 0), new Vector3D(1, 0, 0));
failures += Check("fully removed", Area(m2), 0.0);
// At x=5, the retained trapezoid has area 50 - 12.5 = 37.5.
// case 3: 平面 x=5 → 保留 x≤5:梯形面積 = 50 - 右側小三角形(底5高5)/2 = 50 - 12.5 = 37.5
var m3 = SectionService.ClipMesh(mesh, new Point3D(5, 0, 0), new Vector3D(1, 0, 0));
failures += Check("half clipped (x<=5)", Area(m3), 37.5);
foreach (Point3D p in m3.Positions)
if (p.X > 5 + 1e-9) { Console.WriteLine($" FAIL: vertex x={p.X} is outside retained side"); failures++; }
// Reverse the plane normal to retain the smaller triangle.
// case 4: 反向(保留 x≥5)→ 12.5
var m4 = SectionService.ClipMesh(mesh, new Point3D(5, 0, 0), new Vector3D(-1, 0, 0));
failures += Check("half clipped (x>=5)", Area(m4), 12.5);
// Clip a line segment.
// case 5: 線段裁切
var segs = new Point3DCollection { new(0, 0, 0), new(10, 0, 0) };
Point3DCollection s1 = SectionService.ClipSegments(segs, new Point3D(4, 0, 0), new Vector3D(1, 0, 0));
double segLen = s1.Count == 2 ? (s1[1] - s1[0]).Length : -1;
+3 -3
View File
@@ -5,13 +5,13 @@ using System.Linq;
using CADability;
using CADability.GeoObject;
using STPViewer.Services;
using IOPath = System.IO.Path; // Avoid the CADability.GeoObject.Path name collision.
using IOPath = System.IO.Path; // CADability.GeoObject.Path(曲線)撞名
namespace SmokeTest;
/// <summary>
/// Headless STEP export round trip: import, export through the same CADability path,
/// then import the result and compare solid counts.
/// STEP 匯出往返驗證(v0.5.0 匯出 STEP 功能的無 UI 驗證):
/// 匯入來源 STEP → 走與 MainViewModel.ExportStepFile 相同路徑寫出新檔 → 重新匯入比對實體數。
/// </summary>
public static class ExportTest
{
+5 -5
View File
@@ -1,4 +1,4 @@
// Interference checks for intersecting, separated, and touching meshes.
// 干涉檢查數學驗證:相交 / 分離 / 貼合三種情境
using System.Windows.Media;
using System.Windows.Media.Media3D;
using STPViewer.Services;
@@ -11,14 +11,14 @@ internal static class InterferenceTest
{
int failures = 0;
// Two tetrahedra overlap when B is offset by 5 along X.
// 兩個互穿的四面體(B 比 A 平移 +5,邊長 10 → 必相交)
MeshGeometry3D a = Tetra(new Vector3D(0, 0, 0));
MeshGeometry3D b = Tetra(new Vector3D(5, 0, 0));
InterferenceResult r1 = InterferenceService.Check(new[] { a }, new[] { b });
failures += Check("overlap intersects", r1.Intersects, true);
failures += Check("overlap has intersection segments", r1.Segments.Count > 0, true);
// B at X=30 leaves a gap of about 20.
// 分離(B 平移 +30,gap 應 ≈ 30 - 10 = 20)
MeshGeometry3D c = Tetra(new Vector3D(30, 0, 0));
InterferenceResult r2 = InterferenceService.Check(new[] { a }, new[] { c });
failures += Check("separated meshes do not intersect", r2.Intersects, false);
@@ -26,7 +26,7 @@ internal static class InterferenceTest
Console.WriteLine($" {(gapOk ? "PASS" : "FAIL")}: separated gap = {r2.GapDistance:F4} (expected ~20)");
if (!gapOk) failures++;
// B at X=10 touches a vertex without penetrating A.
// 貼合(B 平移 +10,頂點剛好接觸 → 不算穿透、gap ≈ 0)
MeshGeometry3D d = Tetra(new Vector3D(10, 0, 0));
InterferenceResult r3 = InterferenceService.Check(new[] { a }, new[] { d });
failures += Check("touching is not penetration", r3.Intersects, false);
@@ -45,7 +45,7 @@ internal static class InterferenceTest
return ok ? 0 : 1;
}
/// <summary>A tetrahedron with side length 10, translated by offset.</summary>
/// <summary>邊長 10 的四面體,offset 平移</summary>
private static MeshGeometry3D Tetra(Vector3D offset)
{
Point3D P(double x, double y, double z) => new Point3D(x, y, z) + offset;
+1 -1
View File
@@ -1,4 +1,4 @@
// Generate a small standard DXF test file with the bundled netDxf library.
// 測試輔助:用 CADability 內嵌的 netDxf 產生標準 DXF 測試檔
using netDxf;
using netDxf.Entities;
+2 -2
View File
@@ -2,12 +2,12 @@ using System.IO;
namespace SmokeTest;
/// <summary>Generate a binary STL grid with two triangles per cell and noncoplanar vertices.</summary>
/// <summary>產生測試用 binary STL:一片 gridN×gridN 的網格,每格 2 個三角形(波浪起伏,避免全共面)。</summary>
internal static class MakeStl
{
public static void Run(string path, int triCount)
{
// Two triangles per cell; use a square grid large enough for the requested count.
// 每格 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;
+1 -1
View File
@@ -1,4 +1,4 @@
// Headless smoke tests for CAD import, triangulation, and the assembly tree.
// 無 UI smoke test:驗證 CAD 檔解析 → 三角化 → 裝配樹 整條匯入管線
using System.Diagnostics;
using System.IO;
using STPViewer.Services;
+14 -11
View File
@@ -5,13 +5,15 @@ using System.Text;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using STPViewer.Services;
using IOPath = System.IO.Path; // Avoid the CADability.GeoObject.Path name collision.
using IOPath = System.IO.Path; // CADability.GeoObject.Path(曲線)撞名
namespace SmokeTest;
/// <summary>
/// Headless STL export checks:
/// binary round trip, ASCII facet structure, and degenerate triangle filtering.
/// STL 匯出驗證(無 UI):
/// 1) binary — 已知立方體網格寫出 → 解析 header/三角形數/座標範圍比對(含縮放)
/// 2) ASCII — facet 數與 solid/endsolid 結構
/// 3) 退化三角形(零面積)自動濾除
/// </summary>
public static class StlExportTest
{
@@ -22,9 +24,9 @@ public static class StlExportTest
string dir = IOPath.Combine(IOPath.GetTempPath(), "stpviewer_stl_test");
Directory.CreateDirectory(dir);
MeshGeometry3D cube = BuildCube(10); // 10x10x10 mm, 12 triangles.
MeshGeometry3D cube = BuildCube(10); // 10×10×10 mm、12 三角形
// Binary triangle count and scaled coordinate range.
// 1) binary:三角形數 + 縮放後座標範圍
{
string f = IOPath.Combine(dir, "cube_bin.stl");
double scale = 1.0 / 25.4; // mm to inches.
@@ -35,7 +37,7 @@ public static class StlExportTest
Report(ref failed, ok, $"binary round trip: 12 triangles, inch range [{min:F4}, {max:F4}]");
}
// ASCII facet count and structure.
// 2) ASCII:facet 數 + 結構
{
string f = IOPath.Combine(dir, "cube_ascii.stl");
int written = StlExportService.Write(f, new[] { cube }, ascii: true, scale: 1.0);
@@ -47,12 +49,12 @@ public static class StlExportTest
Report(ref failed, ok, $"ASCII structure valid, facets = {facets}");
}
// A zero-area triangle must be filtered from the export.
// 3) 退化三角形濾除:加一個零面積三角形 → 仍是 12
{
var degenerate = new MeshGeometry3D();
degenerate.Positions.Add(new Point3D(0, 0, 0));
degenerate.Positions.Add(new Point3D(1, 1, 1));
degenerate.Positions.Add(new Point3D(2, 2, 2)); // Collinear: zero area.
degenerate.Positions.Add(new Point3D(2, 2, 2)); // 共線 → 面積 0
degenerate.TriangleIndices.Add(0);
degenerate.TriangleIndices.Add(1);
degenerate.TriangleIndices.Add(2);
@@ -65,7 +67,8 @@ public static class StlExportTest
Report(ref failed, ok, $"degenerate triangle filtered ({written} written)");
}
// Optional real STEP input: retessellate B-rep geometry at fine precision.
// 4)(可選)真實 STEP:B-rep 精細重新三角化 → 寫 binary STL 驗證
//(不測「較粗」:CADability GetTriangulation 對比快取粗的精度回傳既有快取,粗化無效 → 功能已移除)
if (stepPath is not null)
{
var data = new StepImportService().Import(stepPath);
@@ -80,7 +83,7 @@ public static class StlExportTest
(uint count, _, _) = ReadBinary(f);
Report(ref failed, meshes.Count > 0 && written > 0 && count == written,
$"fine retessellation: {meshes.Count} faces, {written:N0} triangles");
// CADability's triangle count is not monotonic in precision; 0.15x is measurably finer.
// CADability 三角形數對精度非單調(0.4× 反而比 1× 少),Fine=0.15× 實測明顯更細
Report(ref failed, written > importTris,
$"fine ({written:N0}) > imported default ({importTris:N0})");
}
@@ -103,7 +106,7 @@ public static class StlExportTest
if (!ok) failed++;
}
/// <summary>Axis-aligned cube from 0 to size, with 12 triangles.</summary>
/// <summary>0..size 的軸對齊立方體,12 個三角形(法向不講究,STL 寫出時自算)</summary>
private static MeshGeometry3D BuildCube(double s)
{
var m = new MeshGeometry3D();
+1 -1
View File
@@ -1,4 +1,4 @@
// Print the STEP object hierarchy after import with HierarchyToBlocks enabled.
// 實驗工具:印出 STEP 匯入後的物件階層(HierarchyToBlocks=true)
using CADability;
using CADability.GeoObject;