本篇目的: 學習如何從使用者需求規劃 GUI 操作流程、模組職責與資料流,並將完成的 Python GUI 打包成 EXE。
在 day 1 時我在第一段宣告"這是一個讓我們製造工程師可以以結構化方式,從 0 開始做出自己的工作用程式“。為了達成這個目的,我們在前面 26 天要理解 Pandas 資料處理、Matplotlib 繪圖、Qt Designer、MVC、Signal & Slot,以及如何用模組化思維設計 class 結構做出功能管理器。
我們知道了 GUI 程式相關的零件怎麼製作,但我們尚未將其組合成一個可以正常運作的 GUI 程式。但如果是首次編寫 GUI 程式的開發者的最大疑問應該是”怎麼樣把不同的功能模組,在 controller 中串聯起來”?
因此 day 27~ day 29 會以編寫一套 wafer 分析 GUI 程式為範例,將前面介紹的技術整合起來,並分享如何規劃專案架構,以及如何善用生成式 AI 協助開發。

(連上天都被拉姆與雷姆的可愛,迷的神魂顛倒呢)
在開始 coding 之前,我們應該先思考:**User 希望透過這套工具完成什麼工作?**關於這點我們應該最清楚不過,不過以下的流程可以協助我們將資料處理的流程想得更加清楚。
假設我們或 user 需要以下功能:
Wafer 良率分析:批次讀取檢測檔案,設定 SPEC 與 Bin,產生良率統計報表。
Wafer Mapping:選擇 Wafer 與測項,繪製 Mapping,並透過滑鼠移動即時監看晶粒數值。
我們可以針對這些目的,列出我們程式內應該要有的功能,接著將需求轉換成實際的操作流程。
| 功能 | 使用者操作 | 程式需要提供的能力 |
|---|---|---|
| 良率分析 | ① 選取多個 Wafer CSV | 批次讀取與格式檢查 |
| 良率分析 | ② 勾選分析對象 | 動態生成 Wafer 勾選框 |
| 良率分析 | ③ 輸入 SPEC 與 Bin | 驗證及套用使用者設定 |
| 良率分析 | ④ 執行計算、查看報表 | 統計運算與表格顯示 |
| 良率分析 | ⑤ 複製或輸出結果 | TSV 複製與 XLSX 輸出 |
| Mapping | ① 選擇 Wafer、測項及色階 | 資料整理、設定驗證 |
| Mapping | ② 檢視 Mapping | 嵌入式繪圖與格線控制 |
| Mapping | ③ 移動滑鼠查看晶粒 | 座標查詢與即時數值更新 |
| Mapping | ④ 輸出 Mapping 圖片 | PNG 圖片輸出 |
這裡還不會去規劃模組的功能區分。我們只是先確認要做什麼,再決定程式需要具備哪些能力,才不會一開始就把所有功能塞進同一份程式。
確定功能需求後,接下來要思考這些工作應該如何分成不同模組。我們可以參照 day 15 介紹過的 MVC 與關注點分離(Separation of Concerns),這次就將相同概念應用在完整 GUI 專案中。

這裡將程式分為四種主要職責:
Entry Point:負責啟動 GUI 與初始化 Controller。
Controller:負責使用者事件、資料狀態與工作流程。
View / UI Manager:負責畫面顯示、元件管理與互動。
Cal / Model:負責資料讀取、驗證與計算。
因為本次功能較多,因此我們需要根據不同的工作內容分出不同的 py 檔案。例如同樣都是資料處理,處理成繪圖格式與處理成報表格式當然就會分2個 py 檔,MVC 並不代表整套程式只能有三個 Python 檔案**。**以下是本次範例的實際專案結構:
w27-w29_a_example/
│
├── main.py # 啟動 GUI、建立主視窗與 Controller
├── controller.py # 連接事件、保存資料與調度功能模組
├── cal.py # 批次讀取 Wafer CSV 與檢查檔案
├── ui.ui # Qt Designer 原始介面設計檔
│
├── ui/
│ ├── UI.py # Qt Designer 產生的 GUI 元件與排版
│ ├── DynamicCheckboxManager.py # 動態建立、清除與管理 Wafer 勾選框
│ ├── KBshortCutOnQtable.py # 表格複製快捷鍵與剪貼簿管理
│ └── YieldTableManager.py # 百分比報表顯示與 TSV 產生
│
├── map_module/
│ ├── mapping_cal.py # Mapping 資料整理與色階驗證、計算
│ └── WaferMapView.py # Matplotlib 嵌入、滑鼠查值與 PNG 輸出
│
└── yield_cal/
└── yield_spec.py # SPEC/Bin 驗證、Yield 計算與 XLSX 輸出
以 Mapping 為例,mapping_cal.py 負責數據整理與色階計算,WaferMapView.py 則負責圖表顯示及滑鼠互動。雖然兩者都屬於 Mapping 功能,但工作性質不同,因此分開管理。
程式的進入點 (main.py) 在每個專案中都差不多。main.py 只負責建立介面與 controller,不需要知道 yield 如何計算,或 mapping 如何繪製。
把程式拆成不同檔案,只是模組化的第一步。更重要的是決定:資料由誰保存、如何傳遞,以及各模組負責哪些工作。
以本範例來說,yield 與 mapping 都是取用 wafer 檢測資料只是後續作不同處置。因此可以共用資料讀取功能,與共用同一個讀取後的檢測資料。
我們最好為同一階層、接收相同資料來源之模組(如同樣接收點測資料的繪圖與計算模組),設計相同資料介面。這樣做好處是多個模組共用一個讀取後的資料,減少作業時間且避免資料佔據我們又貴又小的記憶體。

在 controller 中,我們使用字典保存讀取完成的 wafer 資料:
self.wafer_data: dict[str, pd.DataFrame] = {}
實際讀取 csv 並輸出上面格式的流程與 function 放在 cal.py 中,我們只需要在 controller 中呼叫它:
def read_wafer_files(
file_paths: list[Path]
) -> dict[str, pd.DataFrame]:
...
這裡的 input 是 csv 路徑清單,output 則是以 wafer ID 為 key 的 DataFrame dict。
這個 function 不需要負責顯示檔案選擇視窗(顯示檔案選擇視窗交給其他 function 了),也不必直接修改 GUI;Controller 只需要取得結果,再交給其他功能模組使用。
因此,模組化不只是分開存放程式碼,更重要的是建立清楚的職責及資料交換方式。當 input、output 與工作範圍都定義清楚,無論自行開發或請 AI 協助實作,後續整合都會容易許多。
對我們產線工程師而言,不太可能去研究每個 Python 套件的細節。現在應該大部分的公司都有配對話式 AI,我們可以用對話式 AI 大幅降低撰寫程式碼的門檻。
但即使完全採用 Vibe Coding 也不能忽略最重要的事情:資料從哪裡來、經過什麼處理、最後產生什麼結果?
我們可能很難寫出專業的開發規範,但我們至少知道資料長什麼樣子、該怎麼處理、想拿到什麼。因此我們請 AI 寫 code 時最好提供以下資料給它:
Input:資料來源、格式與必要欄位。
Process:資料清洗、判斷條件與運算流程。
Output:結果的資料格式與數值意義。
例如我們可以這樣描述良率計算需求:
請協助設計一個 Wafer 良率計算 Function。
【Input】
- 輸入為 Pandas DataFrame
- 包含 wafer_id、Po、Vf2、Wd2 欄位
- SPEC 由使用者提供,可單獨設定上限或下限
【Process】
- 根據指定測項的 SPEC 判斷晶粒是否合格
- 數值等於 SPEC 上下限時視為合格
- 良率分母為該片 Wafer 的全部晶粒數
【Output】
- 每片 Wafer 對應一筆 Yield 結果
- 使用 DataFrame 回傳
- Yield 使用 0~100 的百分比表示
請先確認輸入、處理流程、輸出格式及異常情況,
不要直接產生完整程式碼。
我們都不喜歡 AI 一本正經講幹話、輸出金玉其外敗絮其中的內容。程式可以執行,不代表數據符合我們原本的定義。 因此資料流與程式架構一定要掌握在我們手裡,這樣才能驗證計算結果與出錯時定位問題。
這個專案有提供良率計算與 mapping 繪圖的功能,因為良率計算較為簡單,只會在這裡說明功能如何整合進 GUI;而 day 28~29 會介紹如何將 mapping 圖放在 UI 中與用滑鼠擷取圖面值。
使用者讀取 wafer 檢測檔後,可以勾選要分析的晶圓、並設定測項的的 SPEC 與 bin。若 bin 設定正常並完成驗證後,controller 會呼叫 prepare_yield_data 將已勾選的 wafer 資料整理成一份,然後將整份資料傳入 calculate_2d_distribution() 產生二維 bin 分布報表。
data = prepare_yield_data(
{key: self.wafer_data[key] for key in ids}, bins
)
reports = {
item: calculate_wafer_distribution(
data, item, bins[item], specs[item]
)
for item in ("Po", "Vf2", "Wd2")
}
計算完成後,再由 YieldTableManager 將結果顯示在 QTableWidget 中,並提供 ctrl+c 複製功能以及輸出成 xlsx 檔。
實作結果如下圖所示:

透過這種設計,controller 負責資料與流程調度,數據計算及畫面顯示則由不同模組處理。假設我們未來要調整 yield 的計算方式,只要確定資料型別相同,不一定要修改表格顯示的程式。
我們不可能讓同事去 cmd 編譯程式執行 (有些人連 excel 都用的 2266),因此我們需要將專案包成 exe 檔。以下會說明如何使用 PyInstaller 將 Python 專案封裝成可執行檔。
首先在專案使用的虛擬環境中安裝 PyInstaller,並於 main.py 所在的資料夾執行以下指令:
python -m pip install pyinstaller
python -m PyInstaller --onefile --windowed --name WaferAnalysis main.py
各參數的用途如下:
--onefile:將程式與相依檔案封裝成單一執行檔
--windowed:執行 GUI 時不額外顯示 cmd 視窗
--name WaferAnalysis:指定執行檔名稱,可以改成我們喜歡的
main.py:指定程式進入點。
完成後,可以在 dist/ 資料夾找到 exe 檔,不需要將整個 Python 虛擬環境一起交給使用者。
另外成功打包不代表能正常運作,尤其是使用了一些小眾模組。因次我們交付前仍應確認功能是否符合需求。
這次我們從使用者需求出發,規劃 GUI 操作流程、模組分工與資料流,最後將各項功能整合成一套可以交付使用的 Wafer 分析工具。
但我們還有一個功能:mapping 圖即時繪製與滑鼠取值功能。在 day 28~29 中我們會分別說明如何將 matplotlib 的圖內嵌到 UI 中、以及如何運用 matplotlib 的滑鼠事件達到取值功能。