diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 53f438d..f167981 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -463,6 +463,29 @@ WPF retained-mode 每幀重走 visual tree → frame rate 崩。瀏覽模式因
---
+## Development Phases — 第八輪(UI 重整 v0.7.0)
+
+### Phase 16 — 選單列 + 可自訂快速列(工作量:M)
+
+**問題:** 功能累積後單一工具列 30+ 顆按鈕擠在一排,視窗不夠寬就溢出到「▾」,難找難按。
+
+- [x] **選單列**(Menu):全部功能分類下拉 — 檔案(匯入/MRU/四種匯出)、視圖(全覽/標準視圖/正交/單位)、
+ 量測(7 種模式 checkable + 快速鍵提示 + 清除)、變換(對齊/三點/拖曳/操作器/旋轉XYZ)、
+ 工具(干涉/剖面)、快速列(自訂按鈕勾選 + 恢復預設)
+- [x] **快速工具列**(`QuickBarViewModel`):只顯示使用者勾選的常用按鈕;
+ 註冊表定義全部按鈕(key/選單名/預設值),XAML 以索引子綁定
+ `Visibility="{Binding QuickBar[Key].IsChecked, Converter=BoolToVis}"`(按鈕本體與綁定保持靜態)
+- [x] 預設常用:匯入、最近、全覽、等角、正交、點、距離、圓、角度、干涉、剖面、清除、mm⇄in;
+ 其餘(邊/面/面距/對齊/三點/旋轉/拖曳/操作器/四種匯出/前上右視圖)預設收進選單
+- [x] 勾選清單隨 settings.json(`QuickBarKeys`)保存;null/新按鈕用註冊表預設值
+- [x] **剖面參數列**獨立成第三列,只在 `SectionEnabled` 時顯示(含「✕ 關閉」);
+ 平常整條隱藏,不再常駐佔位
+
+**驗收:** 選單所有功能可用(checkable 與工具列 toggle 同步);快速列勾選即時增減、重開保留;
+既有快速鍵 P/D/E/F/C/A/M + Esc 不受影響
+
+---
+
## Future Extensions(下一輪)
- **大檔重開快取** — 匯入成功後把三角網格 + 裝配樹序列化成 sidecar 快取檔(以來源檔 hash 驗證),
diff --git a/CLAUDE.md b/CLAUDE.md
index b7b93be..f83cf17 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -47,6 +47,10 @@ dotnet publish src/STPViewer -c Release -o publish/STPViewer
(量孔對孔 pitch 靠這個)。不要把圓心吸附拿掉或改成永遠優先頂點
- 鍵盤快速鍵在 `MainWindow.Window_PreviewKeyDown`:**焦點在 TextBoxBase/ComboBox 時直接 return**
(樹搜尋框、剖面數值框要能打字),新增輸入控件不用再各自處理
+- UI 三層(v0.7.0):**選單列**(全部功能,分類下拉)+ **快速列**(`QuickBarViewModel` 註冊表,
+ 使用者勾選常用按鈕,XAML 索引子綁定 `QuickBar[Key].IsChecked` 控 Visibility,按鈕本體維持靜態綁定)+
+ **剖面參數列**(只在 SectionEnabled 顯示)。新增功能要三處都接:選單分類、快速列註冊表
+ (含預設值)+ 對應 XAML 按鈕;勾選清單存 settings.json `QuickBarKeys`(null = 預設)
- 使用者設定 `SettingsService`(%LOCALAPPDATA%\STPViewer\settings.json):視窗(MainWindow 管)+
單位/MRU(VM `LoadSettings`/`SaveSettingsInto`);壞檔回預設、儲存失敗靜默
- 匯出 STEP 走 `CADability.ExportStep.WriteToFile(file, Project.CreateSimpleProject()+Model.Add)`;
diff --git a/src/STPViewer/Converters/FileNameConverter.cs b/src/STPViewer/Converters/FileNameConverter.cs
new file mode 100644
index 0000000..924ea12
--- /dev/null
+++ b/src/STPViewer/Converters/FileNameConverter.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace STPViewer.Converters;
+
+/// 完整路徑 → 檔名(選單「最近檔案」顯示用;tooltip 仍給完整路徑)
+public class FileNameConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
+ value is string s ? System.IO.Path.GetFileName(s) : "";
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
+ throw new NotSupportedException();
+}
diff --git a/src/STPViewer/MainWindow.xaml b/src/STPViewer/MainWindow.xaml
index adfc8db..a5f440f 100644
--- a/src/STPViewer/MainWindow.xaml
+++ b/src/STPViewer/MainWindow.xaml
@@ -12,111 +12,249 @@
+
+
-
+
+
+
+
-
-
-
-
-
-
-
-
-
- 正交
-
-
📍 點
📏 距離
📐 邊
⬛ 面
⭕ 圓
∠ 角度
⇔ 面距
-
🎯 對齊
🎯 三點
-
-
-
-
-
🖐 拖曳
- ⊹ 操作器
-
-
-
+
+
+
+
- ✂ 剖面
+
+ VerticalAlignment="Center" ToolTip="剖切軸向(3點 = 在模型上點 3 點定義任意平面)">
X
Y
Z
3點
+ VerticalAlignment="Center" Margin="4,0" ToolTip="剖面位置(沿法向 %)" />
+ VerticalAlignment="Center" ToolTip="剖面位置 %(0–100,可直接輸入,離開欄位生效)" />
+ Margin="4,0" ToolTip="切換保留側" />
+ ✕ 關閉
diff --git a/src/STPViewer/STPViewer.csproj b/src/STPViewer/STPViewer.csproj
index e717461..6125ed3 100644
--- a/src/STPViewer/STPViewer.csproj
+++ b/src/STPViewer/STPViewer.csproj
@@ -6,7 +6,7 @@
enable
enable
true
- 0.6.0
+ 0.7.0
diff --git a/src/STPViewer/Services/SettingsService.cs b/src/STPViewer/Services/SettingsService.cs
index 245cc1d..139cdfc 100644
--- a/src/STPViewer/Services/SettingsService.cs
+++ b/src/STPViewer/Services/SettingsService.cs
@@ -15,6 +15,9 @@ public class AppSettings
public bool WindowMaximized { get; set; }
public bool UseInch { get; set; }
public List RecentFiles { get; set; } = new();
+
+ /// 快速工具列顯示的按鈕 key 清單;null = 用預設(QuickBarViewModel.Registry)
+ public List? QuickBarKeys { get; set; }
}
/// 設定讀寫:壞檔/缺檔一律回預設值,儲存失敗靜默(設定不值得讓程式閃退)
diff --git a/src/STPViewer/ViewModels/MainViewModel.Section.cs b/src/STPViewer/ViewModels/MainViewModel.Section.cs
index 95311cb..5b960f2 100644
--- a/src/STPViewer/ViewModels/MainViewModel.Section.cs
+++ b/src/STPViewer/ViewModels/MainViewModel.Section.cs
@@ -5,6 +5,7 @@ using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
using HelixToolkit.Wpf;
using STPViewer.Models;
using STPViewer.Services;
@@ -38,6 +39,10 @@ public partial class MainViewModel
[ObservableProperty]
private bool sectionFlip;
+ /// 剖面參數列的「✕ 關閉」按鈕(參數列只在剖面開啟時顯示)
+ [RelayCommand]
+ private void CloseSection() => SectionEnabled = false;
+
// ── 3點自訂剖切平面(SectionAxisIndex == 3,v0.5.0)──
private const int CustomAxisIndex = 3;
private Vector3D? _customNormal; // null = 尚未定義(等使用者點 3 點)
diff --git a/src/STPViewer/ViewModels/MainViewModel.cs b/src/STPViewer/ViewModels/MainViewModel.cs
index c87a920..2883c8a 100644
--- a/src/STPViewer/ViewModels/MainViewModel.cs
+++ b/src/STPViewer/ViewModels/MainViewModel.cs
@@ -86,6 +86,9 @@ public partial class MainViewModel : ObservableObject
public ObservableCollection Roots { get; } = new();
public ObservableCollection Measurements { get; } = new();
+ /// 快速工具列自訂(選單列有全部功能,快速列只放勾選的常用按鈕)
+ public QuickBarViewModel QuickBar { get; } = new();
+
// ─── 使用者設定(視窗由 MainWindow 處理;VM 管單位 + MRU)─────
public void LoadSettings(AppSettings s)
@@ -94,12 +97,14 @@ public partial class MainViewModel : ObservableObject
RecentFiles.Clear();
foreach (string f in s.RecentFiles.Where(File.Exists).Take(SettingsService.MaxRecentFiles))
RecentFiles.Add(f);
+ QuickBar.Load(s.QuickBarKeys);
}
public void SaveSettingsInto(AppSettings s)
{
s.UseInch = UseInch;
s.RecentFiles = RecentFiles.ToList();
+ s.QuickBarKeys = QuickBar.ToKeys();
}
private void TouchRecent(string path)
diff --git a/src/STPViewer/ViewModels/QuickBarViewModel.cs b/src/STPViewer/ViewModels/QuickBarViewModel.cs
new file mode 100644
index 0000000..d3075d2
--- /dev/null
+++ b/src/STPViewer/ViewModels/QuickBarViewModel.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+
+namespace STPViewer.ViewModels;
+
+/// 快速列單一按鈕的顯示開關(選單「快速列 → 自訂按鈕」的 checkbox 項)
+public partial class QuickBarItemViewModel : ObservableObject
+{
+ public string Key { get; }
+ public string Label { get; }
+ public bool Default { get; }
+
+ [ObservableProperty]
+ private bool isChecked;
+
+ public QuickBarItemViewModel(string key, string label, bool @default)
+ {
+ Key = key;
+ Label = label;
+ Default = @default;
+ isChecked = @default;
+ }
+}
+
+///
+/// 快速工具列自訂:所有功能都在選單列,快速列只放使用者勾選的常用按鈕。
+/// XAML 以索引子綁定:Visibility="{Binding QuickBar[Point].IsChecked, Converter=…}"。
+/// 勾選狀態隨 settings.json(QuickBarKeys)保存;null = 用預設。
+///
+public partial class QuickBarViewModel : ObservableObject
+{
+ /// 註冊表 = 快速列順序 + 選單顯示名 + 是否預設顯示(一般 user 常用)
+ private static readonly (string Key, string Label, bool Default)[] Registry =
+ {
+ ("Import", "📂 匯入", true),
+ ("Recent", "▾ 最近檔案", true),
+ ("ZoomAll", "🔍 全覽", true),
+ ("ViewIso", "視圖:等角", true),
+ ("ViewFront", "視圖:前", false),
+ ("ViewTop", "視圖:上", false),
+ ("ViewRight", "視圖:右", false),
+ ("Ortho", "正交投影", true),
+ ("Point", "📍 點", true),
+ ("Distance", "📏 距離", true),
+ ("Edge", "📐 邊", false),
+ ("Face", "⬛ 面", false),
+ ("Circle", "⭕ 圓", true),
+ ("Angle", "∠ 角度", true),
+ ("FaceDistance", "⇔ 面距", false),
+ ("Align", "🎯 兩點對齊", false),
+ ("Align3", "🎯 三點對齊", false),
+ ("RotateX", "旋轉 ↻X", false),
+ ("RotateY", "旋轉 ↻Y", false),
+ ("RotateZ", "旋轉 ↻Z", false),
+ ("Drag", "🖐 拖曳", false),
+ ("Gizmo", "⊹ 操作器", false),
+ ("Interference", "🧩 干涉", true),
+ ("Section", "✂ 剖面", true),
+ ("Clear", "🧹 清除量測", true),
+ ("Unit", "mm ⇄ in", true),
+ ("ExportCsv", "💾 匯出 CSV", false),
+ ("Screenshot", "📷 截圖", false),
+ ("ExportStep", "📤 匯出 STEP", false),
+ ("ExportStl", "📐 匯出 STL", false),
+ };
+
+ private readonly Dictionary _byKey;
+
+ /// 依快速列順序的全部項目(選單自訂清單用)
+ public IReadOnlyList Items { get; }
+
+ public QuickBarViewModel()
+ {
+ Items = Registry.Select(r => new QuickBarItemViewModel(r.Key, r.Label, r.Default)).ToList();
+ _byKey = Items.ToDictionary(i => i.Key);
+ }
+
+ /// XAML 索引子綁定入口:QuickBar[Point].IsChecked
+ public QuickBarItemViewModel this[string key] => _byKey[key];
+
+ /// 套用保存的勾選清單;null = 維持預設(含未知 key 容錯:新版本新增按鈕用預設值)
+ public void Load(List? visibleKeys)
+ {
+ if (visibleKeys is null) return;
+ var set = new HashSet(visibleKeys, StringComparer.Ordinal);
+ foreach (QuickBarItemViewModel item in Items)
+ item.IsChecked = set.Contains(item.Key);
+ }
+
+ /// 目前勾選清單(存 settings.json)
+ public List ToKeys() =>
+ Items.Where(i => i.IsChecked).Select(i => i.Key).ToList();
+
+ [RelayCommand]
+ private void ResetDefaults()
+ {
+ foreach (QuickBarItemViewModel item in Items)
+ item.IsChecked = item.Default;
+ }
+}