commit 0e19dbae1bfa9d7e2c88569e166e388e4fe20ae3 Author: ETWen Date: Sat Jun 13 00:16:10 2026 +0800 feat: Initial release v0.1.0 STP/STEP 3D viewer (WPF, .NET 8) - CADability + HelixToolkit.Wpf: - Multi-file import (STEP/STL/DXF) with STEP assembly tree (per-node visibility/color cascade, zoom-to) - Measurements: point / distance / edge / face / circle / angle / face-to-face distance (B-rep exact where available) - Assembly verification: two-point align (translate), 3-point align (rotate+translate via RigidAlign), axis rotate 90, interference check (tri-tri intersection + min gap) - Section view (CPU mesh clipping, originals preserved) - mm/inch toggle, CSV export (UTF-8 BOM), 2x PNG screenshot - Performance: per-file merged edge lines, per-leaf merged mesh in browse mode, no BackMaterial on closed solids, edge suspend during camera interaction, parallel leaf triangulation with retry - SmokeTest CLI: import pipeline, --tree, --clip-test, --interference-test, --align-test (all passing) Co-Authored-By: Claude Opus 4.8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b70215 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# .NET +bin/ +obj/ +publish/ +*.user +.vs/ + +# secret/ 全部忽略,但保留 README 與範本 +secret/* +!secret/README.md +!secret/*.example +!secret/.gitkeep + +# AI 協作素材不入 git +For_AI/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..64ab66e --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,326 @@ +# STPViewer — Architecture + +> STP/STEP 3D 檢視器:多檔匯入、圖層管理、點/線/面/圓量測(C# .NET 8 WPF 桌面程式) + +--- + +## Overview + +STPViewer 是一套 Windows 桌面 3D CAD 檢視工具,給硬體 / SI 工程師快速開啟機構件的 +STEP(`.stp` / `.step`)檔案做確認與量測,不需要安裝 SolidWorks / Creo 等重量級 CAD。 + +核心價值: + +- **多檔匯入**:一次載入多個 CAD 檔(STEP / STL / DXF),每個檔案自成一棵樹 +- **裝配樹**:STEP product structure 還原成樹狀節點(組件→零件),逐節點 顯示/隱藏、換色、Zoom-to +- **量測**:點座標、兩點距離、邊長、面(面積/類型/法向量)、圓(心/半徑/直徑/周長)、 + 兩面/兩邊夾角、面到面最短距離;單位 mm ⇄ inch 即時切換 +- **剖面**:X/Y/Z 軸向剖切,位置滑桿 + 反向,CPU 網格裁切(量測不受影響) +- **匯出**:量測結果 CSV、3D 視圖 PNG 截圖(2x) +- 單機離線執行,無網路相依 + +使用者:單一桌面使用者(無登入 / 角色系統)。 + +--- + +## Tech Stack + +| Layer | Technology | 說明 | +|---|---|---| +| Runtime | .NET 8(`net8.0-windows`) | 桌面 WPF | +| UI Framework | WPF + MVVM(CommunityToolkit.Mvvm) | | +| 3D 渲染 / 拾取 | **HelixToolkit.Wpf** | Viewport3D 封裝、相機操作、HitTest | +| STEP 解析 / 幾何核心 | **CADability**(純 C#,netstandard2.0) | STEP B-rep 匯入、Face 三角化、邊/面幾何查詢 | +| 量測幾何運算 | CADability(B-rep 精確值)+ 網格近似(面積) | | +| 打包 | `dotnet publish -c Release` | 免安裝、單資料夾 | + +> 技術選型備註:OpenCASCADE 的 .NET wrapper(Macad.Occt 等)不在 NuGet 上, +> 自建 C++/CLI wrapper 成本過高;CADability 是純 C# 的 CAD kernel(MIT), +> 內建 STEP reader 與三角化,與 .NET 8 相容(netstandard2.0),故採用。 + +--- + +## Architecture Diagram + +``` +┌────────────────────────── MainWindow (WPF) ──────────────────────────┐ +│ Toolbar(匯入/量測模式) LayerPanel HelixViewport3D 量測結果面板 │ +└──────────────┬───────────────┬───────────────┬───────────────────────┘ + │ ICommand │ binding │ MouseDown(HitTest) + ▼ ▼ ▼ +┌──────────────────────── MainViewModel (MVVM) ────────────────────────┐ +│ Layers: ObservableCollection │ +│ Measurements: ObservableCollection │ +│ CurrentMode: None/Point/Distance/Edge/Face/Circle │ +└───────┬──────────────────────────────┬───────────────────────────────┘ + ▼ ▼ +┌─ StepImportService ─────────┐ ┌─ MeasurementService ───────────────┐ +│ CADability ImportStep.Read │ │ Hit → GeometryModel3D → FaceInfo │ +│ Solid→Shell→Face 三角化 │ │ 點: 頂點吸附 / 表面點 │ +│ Edge 取樣折線(輪廓線) │ │ 邊: 最近 Edge → Line/Ellipse 判型 │ +│ → MeshGeometry3D + FaceInfo │ │ 面: 面積(網格Σ)+Surface 類型 │ +└─────────────────────────────┘ │ 圓: 圓形 Edge → 心/半徑/直徑/周長 │ + └────────────────────────────────────┘ +``` + +資料流:`*.stp → CADability B-rep → 三角網格(渲染) + B-rep 參照(量測) → Helix Viewport` + +每個 Face 對應一個 `GeometryModel3D`,並登錄到 `Dictionary`, +HitTest 命中後可反查回 B-rep Face / Edge 做精確量測。 + +--- + +## Project Structure + +``` +STPViewer/ +│ +├── CLAUDE.md # 專案記憶 & 給 Claude 的指令 +├── README.md # 快速上手 +├── ARCHITECTURE.md # 本文件 +├── .gitignore # 含 secret/ 與 For_AI/ 規則 +│ +├── docs/ +│ ├── decisions/ # 技術決策紀錄(ADR) +│ └── runbooks/ # 操作手冊 +│ +├── secret/ # 🚫 gitignored(README.md / *.example 除外) +│ ├── README.md +│ ├── run-STPViewer.ps1.example +│ └── run-STPViewer.sh.example +│ +├── For_AI/ # 🚫 gitignored — AI 協作素材 +│ +├── STPViewer.sln +├── *.stp # 根目錄現有測試模型(Amphenol connector) +│ +└── src/ + └── STPViewer/ + ├── STPViewer.csproj # net8.0-windows, UseWPF + ├── App.xaml / App.xaml.cs + ├── MainWindow.xaml / .cs # 版面 + 滑鼠拾取事件 + │ + ├── Models/ + │ ├── FaceInfo.cs # GeometryModel3D ↔ B-rep Face 對照(STL 為 null) + │ ├── MeasureMode.cs # enum: None/Point/Distance/Edge/Face/Circle/Angle/FaceDistance/Align/Align3/Interference + │ ├── MeasurementResult.cs # 量測結果(雙單位 lambda、3D 標籤同步) + │ └── UnitSystem.cs # mm/inch + Units 格式化 + │ + ├── Services/ + │ ├── StepImportService.cs # STEP/STL/DXF 讀檔 + 三角化 + 裝配樹 + │ ├── MeasurementService.cs # 點/線/面/圓/角度/面距 幾何計算 + │ ├── InterferenceService.cs # 干涉檢查:三角形-三角形相交(區間法)+均勻網格加速;無干涉時近似最小間隙 + │ ├── RigidAlign.cs # 三點對齊/旋轉的剛體變換數學(Matrix3D列向量 ↔ ModOp行向量 轉換) + │ └── SectionService.cs # 剖面:網格/線段半空間裁切 + │ + └── ViewModels/ + ├── MainViewModel.cs # 樹集合、量測集合、剖面、單位、匯出 + └── ModelNodeViewModel.cs # 裝配樹節點(可見性/顏色 cascade) +``` + +--- + +## Data Models + +桌面程式無資料庫;核心為記憶體內模型: + +```csharp +// 一個匯入檔 = 一個圖層 +class LayerItemViewModel +{ + string Name; // 檔名(不含路徑) + string FilePath; + bool IsVisible; // 切換 viewport 中的 ModelVisual3D + Color Color; // 圖層色(換色重建材質) + int SolidCount, FaceCount, TriangleCount; + ModelVisual3D BodyVisual; // 面網格 + ModelVisual3D EdgeVisual; // 輪廓線 + Rect3D Bounds; // Zoom-to 用 +} + +// HitTest 反查:渲染物件 → B-rep +class FaceInfo +{ + object Face; // CADability Face(量測用 B-rep) + LayerItemViewModel Owner; + MeshGeometry3D Mesh; // 面積近似 / 頂點吸附 +} + +enum MeasureMode { None, Point, Distance, Edge, Face, Circle, + Angle, FaceDistance, Align, Interference } + +class MeasurementResult +{ + MeasureMode Kind; + string Title; // "P1 (12.30, 4.50, 0.00)" + string Detail; // 多行明細(Δ、半徑、面積…) + List Overlays; // 視圖中的標記(刪除量測時一併移除) +} +``` + +單位:STEP 內部以 mm 為準(CADability 匯入時依檔內單位換算),UI 顯示 mm。 + +--- + +## Key Features + +| 功能 | 操作 | 輸出 | +|---|---|---| +| 匯入 STP | 工具列「匯入」(可複選) / 拖放檔案 | 新圖層 + 自動 ZoomExtents | +| 圖層 | 面板勾選顯示、換色、Zoom-to、移除 | 即時反映於 3D 視圖 | +| 量測-點 | 模式「點」+ 點擊模型 | 座標(優先吸附頂點/邊端點) | +| 量測-距離 | 模式「距離」+ 點兩下 | 直線距離 + ΔX/ΔY/ΔZ + 視圖連線 | +| 量測-邊 | 模式「邊」+ 點擊邊附近 | 線段長/曲線長;圓弧附半徑 | +| 量測-面 | 模式「面」+ 點擊面 | 面積、曲面類型、平面法向量/圓柱半徑 | +| 量測-圓 | 模式「圓」+ 點擊圓孔邊緣 | 圓心、半徑、直徑、周長 + 視圖圓心標記 | +| 量測-角度 | 模式「∠」+ 點兩個面(或靠近直線邊) | 夾角 + 補角(面取法向量、邊取方向) | +| 量測-面距 | 模式「⇔」+ 點兩個面 | 面到面最短距離(網格近似)+ 最近點對連線 | +| 對齊 | 模式「對齊」+ 點「要移動零件」上一點、再點目標點 | 純平移該檔案使點1貼到點2(B-rep 用 `ModOp` 整體位移,量測清空) | +| 三點對齊 | 模式「三點」+ 來源檔 3 點、目標檔 3 對應點 | 旋轉+平移剛體變換(點1精確貼合、1→2 方向對齊、三點平面對齊;`RigidAlign`) | +| 旋轉 | 樹面板選檔案 + 工具列 ↻X/↻Y/↻Z | 繞檔案中心 +90°(連按累加;方向不合時先轉正再對齊) | +| 干涉 | 工具列「🧩 干涉」(需剛好 2 個可見檔案) | 相交→紅色交線+相交三角形對數;無相交→最小間隙 gap(≈0 即配合 match) | +| 剖面 | 工具列「✂ 剖面」+ 軸向/位置/反向 | CPU 裁切渲染網格(原始幾何保留,量測仍精確) | +| 單位 | 工具列 mm ⇄ in | 既有量測(清單+3D 標籤)即時換算 | +| 匯出 | 💾 CSV / 📷 截圖 | UTF-8 BOM CSV;2x PNG | +| 視圖 | 滑鼠右鍵旋轉/滾輪縮放/中鍵平移(Helix 預設)、ViewCube | | + +支援格式:`.stp` / `.step`(B-rep + 裝配樹)、`.stl`(純網格,僅點/距離/角度/面距量測)、 +`.dxf`(線架構檢視)。**IGES 不支援**(CADability 無 IGES reader)。 + +--- + +## Key Constraints & Business Rules + +1. 每個匯入檔案 = 一個圖層;同檔重複匯入產生新圖層(後綴 `(2)`) +2. 量測一律以 **B-rep 幾何** 為準(邊長、圓半徑);僅「面積」用網格加總近似(三角化精度內) +3. 隱藏圖層不參與 HitTest(量測不會打到看不見的東西) +4. 移除圖層時,其上的量測 overlay 一併清除 +5. 三角化精度依物件尺寸自適應(對角線 × 0.0015,限 0.02–0.5 mm);大型組件匯入走背景執行緒,UI 不凍結 +6. 不寫入/修改原始 STP 檔(唯讀檢視器) + +--- + +## Security Considerations + +- 純離線桌面工具,無網路、無帳號、無資料庫 +- 本專案目前無任何 runtime secret;`secret/` 仍依專案慣例建立: + - `secret/*` 全部 gitignore,僅 `README.md`、`*.example` 進 git + - `run-STPViewer.*.example` 為啟動腳本範本(本專案無 DB/ApiKey,腳本僅做 build+run) + - 無 compile-time secret → 不需 `publish-*.example` 腳本 +- `For_AI/` 收 AI 協作素材(截圖、筆記),整夾 gitignore + +--- + +## Build & Setup Steps + +```bash +cd E:/10_AI/STPViewer + +# 還原 + 建置 +dotnet build STPViewer.sln -c Debug + +# 執行 +dotnet run --project src/STPViewer + +# 發佈(免安裝資料夾) +dotnet publish src/STPViewer -c Release -o publish/STPViewer +``` + +NuGet 相依(自動還原):`CADability`、`HelixToolkit.Wpf`、`CommunityToolkit.Mvvm` + +--- + +## Development Phases + +### Phase 1 — 專案骨架 + 3D 視窗(工作量:S) +**目標:** 專案能跑起來,出現含 Helix 3D viewport 的主視窗 +**包含:** +- [x] `STPViewer.sln` + `src/STPViewer/STPViewer.csproj`(net8.0-windows、UseWPF、NuGet 三件套) +- [x] `MainWindow.xaml`:工具列 / 左側圖層面板 / 中央 `HelixViewport3D`(含 ViewCube、預設光源)/ 右側量測面板 / 底部狀態列 +- [x] `MainViewModel.cs` 空殼 + DataContext 接線 +**驗收條件:** `dotnet run` 開出主視窗,3D 區可旋轉縮放(空場景 + 格線) + +### Phase 2 — STEP 匯入與渲染(工作量:L) +**目標:** 可開啟單一 STP 並看到實體模型 +**包含:** +- [x] `StepImportService.cs`:CADability `ImportStep` 讀檔 → Solid/Shell/Face 三角化 → `MeshGeometry3D` +- [x] Face→`GeometryModel3D` 一對一、建 `FaceInfo` 對照字典 +- [x] Edge 取樣折線 → `LinesVisual3D` 輪廓線(CAD 外觀) +- [x] 匯入走 `Task.Run`,完成後 UI 執行緒組 Visual + `ZoomExtents` +- [x] 用根目錄 Amphenol STP 驗證 +**驗收條件:** 匯入 Amphenol STP 顯示正確 3D 模型(含輪廓線),視角操作流暢 + +### Phase 3 — 圖層系統(工作量:M) +**目標:** 多檔匯入、各自成層、可管理 +**包含:** +- [x] 「匯入」支援複選 + 檔案拖放 +- [x] `LayerItemViewModel.cs`:名稱、可見性 checkbox、色塊(調色盤換色)、統計(Solid/Face/三角形數) +- [x] 圖層操作:顯示/隱藏(含 HitTest 排除)、Zoom-to、移除(連帶清 overlay) +**驗收條件:** 匯入 2+ 個 STP,逐層開關/換色/移除皆即時生效 + +### Phase 4 — 量測功能(工作量:L) +**目標:** 點 / 距離 / 邊 / 面 / 圓 五種量測可用 +**包含:** +- [x] `MeasurementService.cs` + `MeasureMode` 工具列切換(互斥 toggle) +- [x] 點:HitTest 命中點 + 頂點/邊端點吸附;距離:兩點 + ΔXYZ + 視圖連線 +- [x] 邊:命中面最近 Edge,`Line`→長度、`Ellipse(IsCircle)`→弧長+半徑、其他→曲線長 +- [x] 面:網格面積加總 + Surface 類型(平面法向量 / 圓柱半徑) +- [x] 圓:搜尋最近圓形 Edge → 圓心/半徑/直徑/周長 + 圓心標記 +- [x] 量測結果面板:清單 + 單筆刪除 + 全部清除(overlay 同步移除) +**驗收條件:** 對 Amphenol STP 可量出 pin 孔圓徑、殼體面積、兩點距離,數值合理 + +### Phase 5 — 整合收尾(工作量:S) +**目標:** 穩定可交付 +**包含:** +- [x] 錯誤處理(壞檔/非 STEP → 訊息列提示不閃退)、匯入進度提示 +- [x] 狀態列模式提示(「點選第 2 點…」) +- [x] `README.md`、`CLAUDE.md` 完稿 +- [x] `dotnet publish -c Release` 驗證 + smoke test(無 UI 載檔驗證管線) +**驗收條件:** Release 發佈資料夾雙擊可用;載入壞檔不閃退 + +--- + +## Development Phases — 第二輪(Future Extensions 實作,全部完成) + +### Phase 6 — Future Extensions(工作量:L) +- [x] 剖面(Section plane)檢視 — `SectionService` CPU 網格/線段裁切 + 軸向/位置/反向控制 + 半透明剖面指示 +- [x] 角度量測(兩面/兩邊夾角,含補角)、面到面最短距離(頂點→三角形雙向,網格近似) +- [x] 量測結果匯出 CSV(UTF-8 BOM)/ 視圖 PNG 截圖(2x) +- [x] 裝配樹(STEP `HierarchyToBlocks` product structure)取代「一檔一層」,節點層級 顯示/換色/Zoom +- [x] STL / DXF 格式支援(IGES 落空:CADability 無 IGES reader,誠實不支援) +- [x] 量測單位切換 mm ⇄ inch(清單與 3D 標籤即時換算,內部一律存 mm) + +**驗收:** ClipTest 裁切數學 5 項全過;STEP/STL/DXF 三格式 smoke test 通過;UI 端到端存活 + +--- + +## Development Phases — 第三輪(裝配驗證,全部完成) + +### Phase 7 — 配合 / 干涉驗證(工作量:M) +- [x] 兩點對齊(`Align`)— 點「要移動零件」一點 + 目標點 → 純平移整個檔案使其貼合; + B-rep 用 `CADability.ModOp.Translate` 對 Solid/Shell 整體 `Modify`,網格/邊線/邊界同步重建,量測清空 +- [x] 干涉檢查(`InterferenceService`)— 三角形-三角形相交(區間法回傳交線段)+ 均勻網格空間加速; + 相交→紅色交線 overlay;無相交→近似最小間隙 gap(共面貼合不算穿透) +- [x] SmokeTest `--interference-test`:相交 / 分離(gap≈20) / 貼合(gap≈0) 三情境數學驗證 + +**驗收:** InterferenceTest 3 情境全過;兩件 STEP 勾選後可判定干涉或回報配合間隙 + +### Phase 8 — 旋轉對齊(工作量:M) +- [x] 軸向旋轉 — 樹面板選檔案 + 工具列 ↻X/↻Y/↻Z,繞檔案 Bounds 中心 +90°(方向不合先轉正) +- [x] 三點對齊(`Align3`)— 來源檔 3 特徵點 → 目標檔 3 對應點,解旋轉+平移剛體變換一次貼合 +- [x] `RigidAlign` 數學服務 — `TryRigidTransform`(座標架法)、`ToModOp`(WPF Matrix3D 列向量 ↔ CADability ModOp 行向量轉置) +- [x] 通用 `TransformRoot`(取代平移專用路徑):B-rep ModOp Modify + 網格/合併網格/邊線重算 + `RecomputeBounds` +- [x] SmokeTest `--align-test`:已知變換還原(誤差 ~1e-15)、ModOp↔Matrix3D 一致、共線拒絕 + +**驗收:** AlignTest 8 項全過;公母連接器可旋轉擺正後三點對齊插合,再用干涉檢查驗證配合 + +--- + +## Future Extensions(下一輪) + +- 剖切面封口(cap)填實(目前剖開處可見內部背面材質) +- 樹節點三態 checkbox(部分子節點隱藏時顯示中間態) +- IGES 支援(需引入其他幾何核心或自寫 reader) +- 量測結果匯出含截圖的 PDF 報告 +- 兩邊最短距離、邊到面距離 +- 視圖狀態(相機、圖層、量測)存檔/還原 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e129826 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# STPViewer — Claude 專案記憶 + +## 專案簡介 + +CAD 3D 檢視器(Windows 桌面 WPF, .NET 8):STEP/STL/DXF 匯入、STEP 裝配樹、 +點/距離/邊/面/圓/角度/面距量測、兩點對齊(平移)、三點對齊(旋轉+平移)、軸向旋轉 90°、 +干涉檢查、剖面、mm⇄inch、CSV/截圖匯出。 +詳細設計見 [ARCHITECTURE.md](ARCHITECTURE.md)。 + +## 技術棧 + +- .NET 8 WPF(`net8.0-windows`)、MVVM(CommunityToolkit.Mvvm) +- **CADability**(純 C# CAD kernel):STEP 匯入、B-rep 幾何、Face 三角化 +- **HelixToolkit.Wpf**:3D viewport、相機、HitTest + +## 常用指令 + +```bash +dotnet build STPViewer.sln +dotnet run --project src/STPViewer +dotnet publish src/STPViewer -c Release -o publish/STPViewer +``` + +測試模型:根目錄 `Amphenol RA PHD GPD20-50075_RevB_DC (for Cable).stp` + +## 開發慣例 + +- MVVM:邏輯寫 ViewModel/Service,code-behind 只做純 View 事件(滑鼠拾取轉發) +- 一個 B-rep Face = 一個 `GeometryModel3D`,用 `Dictionary` 反查(量測拾取靠這個,**不可移除逐面結構**) +- 渲染雙模式(降 draw call):每個 leaf 同時持有 `FacesContent`(逐面,量測用)與 `MergedContent`(整零件合併成 1 個 `GeometryModel3D`,瀏覽用)。 + `ApplyRenderMode()` 依狀態切 `BodyVisual.Content`:**瀏覽(None)且未剖面 → 合併網格**;量測模式或剖面 → 逐面。 + 合併網格只代表「未剖切、目前位置」幾何;平移後呼叫 `RebuildMerged(leaf)` 同步。`_faceMap`/量測/剖面/干涉一律走 `FacesContent`,與目前顯示哪種內容無關 +- 合併網格的 `BackMaterial`:封閉實體(`SolidCount≥1` 且 `HasBrep`)**不設**(WPF 兩面渲染成本砍半);開放殼/STL 才設。逐面 `FacesContent` 一律保留 BackMaterial(剖切要看內部) +- 量測值以 B-rep 為準(圓半徑、邊長、角度),面積/面距用網格近似 +- 量測文字一律 `Func` 延後產生(mm⇄inch 即時切換);內部數值永遠存 mm +- 裝配樹節點(`ModelNodeViewModel`)的可見性/邊線/顏色向下 cascade +- 剖面只換 `GeometryModel3D.Geometry`(`FaceInfo.Mesh` 保留原始 frozen mesh 供還原與量測) +- 剛體變換(兩點對齊/旋轉 90°/三點對齊)統一走 `TransformRoot(root, ModOp, Matrix3D)`:B-rep 用 ModOp 對 Solid/Shell 整體 `Modify` + (勿逐面位移,會重複位移共用邊),網格/合併網格/邊線/邊界同步重算;變換後量測已失效要 `ClearMeasurements()`。 + **op 與 m 必須是同一個變換** — 數學在 `Services/RigidAlign.cs`:WPF `Matrix3D` 是「列向量」約定、CADability `ModOp` 是「行向量」約定, + `ToModOp` 負責轉置轉換,改動務必跑 `SmokeTest --align-test` 驗證兩種表示一致,否則 B-rep 與顯示網格會悄悄分家 +- 干涉/面距/對齊等運算在背景執行緒;`Freeze()` 幾何後才跨執行緒 +- 匯入在背景執行緒;`Freeze()` 幾何後才跨執行緒 +- Commit 格式:Conventional Commits(`feat:` / `fix:` / `docs:` …) + +## 注意事項 / 已知限制 + +- `Path` 在 service 會與 `CADability.GeoObject.Path` 撞名 → 用 `IOPath` alias +- CADability 解析大 STEP 慢(39MB/64k 面實測:解析約 276 秒 + 幾何處理),不要改成同步呼叫。 + 解析(`ImportStep.Read`)單執行緒無解;三角化/邊取樣已按 **leaf 平行化**(`_leafWork` 收集 → `Parallel.ForEach`)。 + **平行粒度只能到 leaf**:同 leaf 的面共用 Edge 物件,面級平行會 race。空 leaf 由 `Prune` 收掉(延後三角化可能全失敗)。 + 平行下 `GetTriangulation` 偶發失敗(實測 64k 面丟 ~8 面,跨 leaf 仍有共享狀態)→ 失敗面收進 `_retry`,平行結束後**循序重試**補回, + 該 leaf 的 `FinishLeaf` 也延到重試後才跑。**不要移除重試機制**,也不要把平行度開到面級 +- `StepImportService.Progress` 回報匯入階段(解析/三角化耗時),UI 已接狀態列;訊息來自背景執行緒,要 `Dispatcher.BeginInvoke` +- `LinesVisual3D` 轉動視角逐幀重建,>30k 線段會卡 → 邊線自動關閉邏輯不要移除 +- 邊線採「**一檔一條合併 `LinesVisual3D`**」(掛在 root `EdgeVisual`,由 `RefreshRootEdges` 收集各 leaf `OriginalEdgePoints` 重建)。 + **不要改回逐 leaf 一條** — 裝配樹零件多時,N 條線每幀重建會嚴重卡頓(實測主因)。leaf 只保留邊線「資料」,渲染統一在 root; + 可見性/ShowEdges/剖面/平移變更時呼叫 `RefreshRootEdges(root)` 重組合併線 +- 互動中暫停邊線:`Attach` 掛 `Camera.Changed` → `OnCameraMoved` 隱藏邊線、`_interactionTimer`(180ms) 停下後 `ResumeEdges` 顯示; + `_edgesSuspended` 為真時 `RefreshRootEdges` 不把線掛回。轉動/縮放/平移時不付邊線重建成本 +- CADability `ImportStep` 對少數 AP242 檔案支援不完整;匯入失敗要 catch 顯示訊息,不可閃退 +- IGES 無 reader;STL 無 B-rep(FaceInfo.BrepFace == null 的分支要保留) +- 不寫入原始檔(唯讀工具);WPF 限 Windows,不要嘗試移植 vbox/Linux +- 干涉檢查需剛好 2 個可見檔案(樹面板勾選);共面貼合(無穿透)不算干涉、gap≈0 視為配合(match) +- SmokeTest 工具:`--tree`(裝配樹)、`--clip-test`(剖切數學)、`--interference-test`(干涉相交/分離/貼合)、 + `--align-test`(三點對齊剛體變換 + ModOp↔Matrix3D 一致性)、`--make-dxf`(產測試檔) +- **絕不要用 PowerShell regex/Set-Content 改 .cs 檔** — Windows PowerShell 5.1 預設編碼會把 UTF-8 中文弄成亂碼(已踩過,靠反編譯 DLL 救回)。文字取代一律用 Edit 工具 + +## secret/ 與 For_AI/ + +- `secret/`:本機敏感資料集中地,`secret/*` gitignored(`README.md`、`*.example` 除外)。 + 本專案無 runtime secret,僅有啟動腳本範本。 +- `For_AI/`:AI 協作素材(截圖、草稿),整夾 gitignored。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..742b22e --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# STPViewer + +STP/STEP 3D 檢視器(Windows 桌面程式,C# .NET 8 WPF)— 多檔匯入、圖層管理、點/距離/邊/面/圓量測。 + +![.NET](https://img.shields.io/badge/.NET-8.0-512BD4.svg) ![Platform](https://img.shields.io/badge/platform-Windows-0078D6.svg) ![UI](https://img.shields.io/badge/UI-WPF-blueviolet.svg) + +## 功能 + +- **多檔匯入**:STEP / STL / DXF — 工具列匯入(可複選)、拖放到視窗、或命令列 `STPViewer.exe a.stp b.stl` +- **裝配樹**:STEP product structure 還原成樹(組件→零件),逐節點 顯示/隱藏、換色(cascade)、Zoom-to;檔案層級可移除、開關輪廓邊線 +- **量測**(工具列切換模式,點擊模型): + | 模式 | 輸出 | + |---|---| + | 📍 點 | XYZ 座標(自動吸附鄰近 B-rep 頂點) | + | 📏 距離 | 兩點直線距離 + ΔX/ΔY/ΔZ | + | 📐 邊 | 直線長 / 曲線長 / 圓弧長+半徑 | + | ⬛ 面 | 面積(網格近似)+ 曲面類型(平面法向量、圓柱半徑/軸向) | + | ⭕ 圓 | 圓心 / 半徑 / 直徑 / 周長 | + | ∠ 角度 | 兩面(法向量)/ 兩直線邊 夾角 + 補角 | + | ⇔ 面距 | 面到面最短距離(網格近似)+ 最近點對 | + | ⤚ 對齊 | 點「要移動零件」一點 + 目標點 → 純平移該檔案使兩點貼合(B-rep 整體位移) | + | 🎯 三點 | 來源檔 3 特徵點 + 目標檔 3 對應點 → 旋轉+平移一次貼合(方向不同也能對) | +- **旋轉**:樹面板選檔案 + 工具列 ↻X/↻Y/↻Z 繞中心 +90°(擺正方向用,連按累加) +- **干涉檢查** 🧩:勾選剛好 2 個可見檔案 → 相交時顯示紅色干涉交線 + 相交三角形對數;無相交時回報最小間隙 gap(gap≈0 即為配合 match,共面貼合不算干涉) +- **剖面**:✂ 開關 + X/Y/Z 軸 + 位置滑桿 + 反向;CPU 網格裁切,原始幾何保留(量測不受影響) +- **單位**:mm ⇄ inch 一鍵切換,既有量測(清單與 3D 標籤)即時換算 +- **匯出**:量測結果 CSV(Excel 中文不亂碼)、3D 視圖 PNG 截圖(2x 解析度) +- **視角**:右鍵旋轉、滾輪縮放、中鍵平移、ViewCube + +## 快速開始 + +```bash +dotnet build STPViewer.sln +dotnet run --project src/STPViewer + +# 發佈免安裝資料夾 +dotnet publish src/STPViewer -c Release -o publish/STPViewer +``` + +無 UI 匯入管線與幾何數學驗證: + +```bash +dotnet run --project tools/SmokeTest -- "path\to\model.stp" # 匯入 + 裝配樹 +dotnet run --project tools/SmokeTest -- --clip-test # 剖切裁切數學 +dotnet run --project tools/SmokeTest -- --interference-test # 干涉 相交/分離/貼合 +dotnet run --project tools/SmokeTest -- --align-test # 三點對齊剛體變換數學 +``` + +## 技術棧 + +| 元件 | 用途 | +|---|---| +| [CADability](https://github.com/SOFAgh/CADability)(純 C#) | STEP 匯入、B-rep 幾何核心、面三角化 | +| [HelixToolkit.Wpf](https://github.com/helix-toolkit/helix-toolkit) | 3D viewport、相機操作、HitTest | +| CommunityToolkit.Mvvm | MVVM | + +量測原則:邊長、圓半徑等取 **B-rep 精確值**;面積為三角網格加總近似(三角化精度依模型尺寸自適應 0.02–0.5 mm)。 + +## 已知限制 + +- 大型 STEP(數千面)匯入需數十秒(CADability 解析成本),匯入期間 UI 有進度提示不凍結 +- 輪廓邊線超過 30,000 線段的檔案預設關閉邊線(WPF LinesVisual3D 轉動視角時效能限制),可在樹面板手動開啟 +- **IGES 不支援**(CADability 無 IGES reader);STL 無 B-rep,僅支援 點/距離/角度/面距 量測;DXF 為線架構檢視 +- 剖切面無封口(cap),剖開處顯示內部背面材質(深灰) +- 面積與面距為三角網格近似值;邊長/圓半徑/角度為 B-rep 精確值 +- 少數 AP242 檔案 CADability 支援不完整,匯入失敗會提示訊息(不閃退) +- 唯讀檢視器,不寫入/修改原始檔案 + +詳細設計與開發 Phase 見 [ARCHITECTURE.md](ARCHITECTURE.md)。 diff --git a/STPViewer.sln b/STPViewer.sln new file mode 100644 index 0000000..fb1e05d --- /dev/null +++ b/STPViewer.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "STPViewer", "src\STPViewer\STPViewer.csproj", "{95832FFD-8DAB-47B8-98BF-9E523B6D329A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{07C2787E-EAC7-C090-1BA3-A61EC2A24D84}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmokeTest", "tools\SmokeTest\SmokeTest.csproj", "{C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Debug|x64.ActiveCfg = Debug|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Debug|x64.Build.0 = Debug|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Debug|x86.ActiveCfg = Debug|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Debug|x86.Build.0 = Debug|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Release|Any CPU.Build.0 = Release|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Release|x64.ActiveCfg = Release|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Release|x64.Build.0 = Release|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Release|x86.ActiveCfg = Release|Any CPU + {95832FFD-8DAB-47B8-98BF-9E523B6D329A}.Release|x86.Build.0 = Release|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Debug|x64.Build.0 = Debug|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Debug|x86.ActiveCfg = Debug|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Debug|x86.Build.0 = Debug|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Release|Any CPU.Build.0 = Release|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Release|x64.ActiveCfg = Release|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Release|x64.Build.0 = Release|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Release|x86.ActiveCfg = Release|Any CPU + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {95832FFD-8DAB-47B8-98BF-9E523B6D329A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {C2A2A7E8-9CB3-43BE-912C-9056AD7F8BFB} = {07C2787E-EAC7-C090-1BA3-A61EC2A24D84} + EndGlobalSection +EndGlobal diff --git a/secret/README.md b/secret/README.md new file mode 100644 index 0000000..676a6b7 --- /dev/null +++ b/secret/README.md @@ -0,0 +1,24 @@ +# secret/ — 本機敏感資料集中地 + +本資料夾除 `README.md` 與 `*.example` 外全部 gitignored(規則見根目錄 `.gitignore`)。 + +## 內含物件 + +| 檔案 | git | 用途 | +|---|---|---| +| `README.md` | ✅ committed | 本說明 | +| `run-STPViewer.ps1.example` | ✅ committed | Windows 啟動腳本範本 | +| `run-STPViewer.sh.example` | ✅ committed | Git Bash 啟動腳本範本 | +| `run-STPViewer.ps1` / `.sh` | 🚫 ignored | 從範本複製後的本機實值版 | + +> STPViewer 為離線桌面工具,目前**沒有任何 DB 密碼 / ApiKey**; +> 腳本僅做 build + run。未來若加入需要 secret 的功能(雲端授權、回報伺服器等), +> 依範本內註解加上 env var 注入。 + +## 第一次使用 + +```powershell +cd secret +Copy-Item run-STPViewer.ps1.example run-STPViewer.ps1 +.\run-STPViewer.ps1 +``` diff --git a/secret/run-STPViewer.ps1.example b/secret/run-STPViewer.ps1.example new file mode 100644 index 0000000..ad5b40c --- /dev/null +++ b/secret/run-STPViewer.ps1.example @@ -0,0 +1,31 @@ +# ══════════════════════════════════════════════════════════════════ +# run-STPViewer.ps1 範本 +# +# 第一次 clone 下來: +# cd secret +# Copy-Item run-STPViewer.ps1.example run-STPViewer.ps1 +# .\run-STPViewer.ps1 +# +# 若 PowerShell 擋執行 script,執行一次: +# Set-ExecutionPolicy -Scope CurrentUser RemoteSigned +# +# secret/ 下除 README.md 和 .example 外皆已 gitignore。 +# ══════════════════════════════════════════════════════════════════ + +$ErrorActionPreference = 'Stop' + +# ─── secret(本專案目前無 DB / ApiKey;未來需要時在此加 env var) ── +# $env:SomeService__ApiKey = '__CHANGE_ME__' + +# ══════════════════════════════════════════════════════════════════ + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = Resolve-Path (Join-Path $ScriptDir '..') + +Push-Location $RepoRoot +try { + dotnet run --project src/STPViewer +} +finally { + Pop-Location +} diff --git a/secret/run-STPViewer.sh.example b/secret/run-STPViewer.sh.example new file mode 100644 index 0000000..150ab0a --- /dev/null +++ b/secret/run-STPViewer.sh.example @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# ══════════════════════════════════════════════════════════════════ +# run-STPViewer.sh 範本(Git Bash on Windows) +# +# 第一次 clone 下來: +# cd secret +# cp run-STPViewer.sh.example run-STPViewer.sh +# chmod +x run-STPViewer.sh +# ./run-STPViewer.sh +# +# secret/ 下除 README.md 和 .example 外皆已 gitignore。 +# ══════════════════════════════════════════════════════════════════ + +set -euo pipefail + +# ─── secret(本專案目前無 DB / ApiKey;未來需要時在此 export) ──── +# export SomeService__ApiKey="__CHANGE_ME__" + +# ══════════════════════════════════════════════════════════════════ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +cd "${REPO_ROOT}" +exec dotnet run --project src/STPViewer diff --git a/src/STPViewer/App.xaml b/src/STPViewer/App.xaml new file mode 100644 index 0000000..ec8525a --- /dev/null +++ b/src/STPViewer/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/src/STPViewer/App.xaml.cs b/src/STPViewer/App.xaml.cs new file mode 100644 index 0000000..66a8db6 --- /dev/null +++ b/src/STPViewer/App.xaml.cs @@ -0,0 +1,13 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace STPViewer; + +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ +} + diff --git a/src/STPViewer/AssemblyInfo.cs b/src/STPViewer/AssemblyInfo.cs new file mode 100644 index 0000000..cc29e7f --- /dev/null +++ b/src/STPViewer/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly:ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/src/STPViewer/Converters/EnumToBoolConverter.cs b/src/STPViewer/Converters/EnumToBoolConverter.cs new file mode 100644 index 0000000..00e0f06 --- /dev/null +++ b/src/STPViewer/Converters/EnumToBoolConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using STPViewer.Models; + +namespace STPViewer.Converters; + +/// +/// MeasureMode ↔ ToggleButton.IsChecked。 +/// ConverterParameter 為模式名稱字串;取消勾選時回到 None。 +/// +public class EnumToBoolConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => + value?.ToString() == parameter as string; + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => + value is true && parameter is string s + ? Enum.Parse(s) + : MeasureMode.None; +} diff --git a/src/STPViewer/MainWindow.xaml b/src/STPViewer/MainWindow.xaml new file mode 100644 index 0000000..41592d4 --- /dev/null +++ b/src/STPViewer/MainWindow.xaml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + 📍 點 + 📏 距離 + 📐 邊 + ⬛ 面 + ⭕ 圓 + ∠ 角度 + ⇔ 面距 + + 🎯 對齊 + 🎯 三點 + + + + + + + + + + mm ⇄ in + + + + + + ✂ 剖面 + + X + Y + Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +