---
# System prepended metadata

title: DXF to CSV

---

## 📄 DXF to CSV or TXT 程式筆記
因為 DXF 檔很難打開，所以想先將 DXF 檔案轉為文字檔案( CVS, TXT ) 再進行閱讀

整體流程：使用 `ezdxf` ，讀取`.dxf` 檔案，並提取特定類型的實體（`LINE`、`LWPOLYLINE`、`POLYLINE`、`TEXT`、`MTEXT`、`ATTRIB`），並拆解 `INSERT`（圖塊）來獲得內部的子物件。

最終，所有提取到的幾何座標與文字將被轉換並儲存到對應的 `.csv` 檔案中。

### 函式分解：`write_header`

在 CSV 檔案的開頭寫入標頭行。

```python
def write_header(f: TextIO):
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `f` | `TextIO` | 一個已開啟並可供寫入的文字檔案物件（即 CSV 檔案）。 |
| **回傳** | `None` | |
| **寫入標頭** | `f.write("Entity Type,Handle,...")` | 向檔案寫入固定的 CSV 標頭字串，定義後續數據的欄位名稱。 |

-----

### 函式拆解：`process_line`

處理 `LINE` (直線) 物件。

```python
def process_line(entity: DXFEntity, f: TextIO) -> int:
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `entity` | `DXFEntity` | 從 `ezdxf` 讀取到的 `LINE` 實體物件。 |
| `f` | `TextIO` | 目標 CSV 檔案的寫入物件。 |
| **回傳** | `int` | 成功回傳 `1`，失敗回傳 `0`，用於主函式計數。 |

#### 程式解析

| 行為 | 程式碼片段 | 說明 |
| :--- | :--- | :--- |
| **獲取座標** | `start = entity.dxf.start`<br>`end = entity.dxf.end` | 從 `LINE` 實體中提取 3D 的「起點」和「終點」座標。 |
| **格式化輸出** | `output_line = (...)`<br>`f.write(output_line)` | 將實體類型、圖層、起點 (X1,Y1,Z1) 和終點 (X2,Y2,Z2) 格式化為 CSV 的一行並寫入檔案。座標格式化到小數點後 4 位。 |

-----

### 函式拆解：`process_lwpolyline`

處理 `LWPOLYLINE` (2D 輕量級聚合線)，並將其**拆分為多條獨立線段**。

```python
def process_lwpolyline(entity: DXFEntity, f: TextIO) -> int:
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `entity` | `DXFEntity` | `LWPOLYLINE` 實體物件。 |
| `f` | `TextIO` | 目標 CSV 檔案的寫入物件。 |
| **回傳** | `int` | 成功處理的**線段數量**。 |

#### 程式解析

| 行為 | 程式碼片段 | 說明 |
| :--- | :--- | :--- |
| **獲取 2D 頂點** | `points: list = entity.get_points(format='xy')` | `LWPOLYLINE` 是 2D 的，使用 `get_points` 獲取所有 `(x, y)` 頂點列表。 |
| **獲取 Z 座標** | `elevation = entity.dxf.get('elevation', 0.0)` | 獲取該聚合線的「高程」(Elevation) 屬性，它將作為所有頂點的 Z 座標。 |
| **拆分線段** | `for i in range(len(points) - 1): ... f.write(...)` | 遍歷頂點列表，將每兩個連續的點 (i 和 i+1) 組合，並配上 `elevation` 的 Z 座標，作為一條 `LWPOLY_SEGMENT` 寫入 CSV。 |
| **處理封閉線段** | `if entity.closed ... f.write(...)` | 檢查聚合線是否「封閉」。如果是，則額外寫入一條從「最後一個點」回到「第一個點」的線段。 |

-----

### 函式拆解：`process_polyline`

處理 `POLYLINE` (3D 聚合線)，將其**拆分為多條獨立線段**。

```python
def process_polyline(entity: DXFEntity, f: TextIO) -> int:
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `entity` | `DXFEntity` | `POLYLINE` 實體物件。 |
| `f` | `TextIO` | 目標 CSV 檔案的寫入物件。 |
| **回傳** | `int` | 成功處理的**線段數量**。 |

#### 程式解析

| 行為 | 程式碼片段 | 說明 |
| :--- | :--- | :--- |
| **獲取 3D 頂點** | `points_coords: list[Vec3] = [v.dxf.location for v in entity.vertices]` | `POLYLINE` 是 3D 的，它本身包含 `VERTEX` 子實體。此行遍歷所有 `entity.vertices` 並直接提取每個頂點的 3D `location` 座標。 |
| **拆分線段** | `for i in range(len(points_coords) - 1): ... f.write(...)` | 遍歷 3D 頂點列表，將每兩個連續的點 (i 和 i+1) 作為一條 `POLY_SEGMENT` 寫入 CSV。 |
| **處理封閉線段** | `if entity.is_closed ... f.write(...)` | 檢查聚合線是否為「封閉」。如果是，則額外寫入一條從「最後一個點」回到「第一個點」的線段。 |

-----

### 函式拆解：`process_text`

統一處理 `TEXT` (單行文字)、`MTEXT` (多行文字) 和 `ATTRIB` (圖塊屬性文字)。

```python
def process_text(entity: DXFEntity, f: TextIO) -> int:
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `entity` | `DXFEntity` | `TEXT`, `MTEXT`, 或 `ATTRIB` 實體物件。 |
| `f` | `TextIO` | 目標 CSV 檔案的寫入物件。 |
| **回傳** | `int` | 成功回傳 `1`，失敗回傳 `0`。 |

#### 程式解析

| 行為 | 程式碼片段 | 說明 |
| :--- | :--- | :--- |
| **判斷類型並取值** | `if entity_type == 'TEXT': ... elif entity_type == 'MTEXT': ...` | 判斷實體類型。`MTEXT` 使用 `.text` 屬性取值，而 `TEXT` 和 `ATTRIB` 使用 `.dxf.text` 屬性。 |
| **清理文字內容** | `text_content = str(...).strip().replace('\n', ' ')` | 標準化文字：去除頭尾空白，並將所有換行符 `\n` 替換為空格，以確保 CSV 格式正確。 |
| **格式化輸出** | `f",,," # X2, Y2, Z2 留空`<br>`f'"{text_content}"\n'` | 將文字的 `insert` 點寫入 `X1,Y1,Z1` 欄位，`X2,Y2,Z2` 欄位留空。**文字內容用雙引號 `"` 包裹**，以防止文字中的逗號破壞 CSV 結構。 |

-----

### 函式拆解：`extract_dxf_data`

**單一檔案處理的核心**。負責載入 DXF 檔案、遍歷實體、處理 `INSERT` (圖塊)，並呼叫相應的 `process_...` 函式。

```python
def extract_dxf_data(dxf_path: str, output_path: str):
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `dxf_path` | `str` | 來源 `.dxf` 檔案的完整路徑。 |
| `output_path` | `str` | 數據匯出 `.csv` 檔案的完整路徑。 |
| **回傳** | `None` | |

#### 程式解析

| 行為 | 程式碼片段 | 說明 |
| :--- | :--- | :--- |
| **載入模型空間** | `doc = ezdxf.readfile(dxf_path)`<br>`msp = doc.modelspace()` | 讀取 DXF 檔案並獲取其 Modelspace。 |
| **遍歷頂層實體** | `for entity in msp:` | 迭代模型空間中的所有**頂層**實體。 |
| **處理 Block** | `if entity_type == 'INSERT':` | 檢查實體是否為 `INSERT`。|
| **拆解** | `for sub_entity in entity.explode():` | 呼叫 `entity.explode()` 將圖塊拆解為其組成的基本物件（如 LINE, TEXT），並自動應用圖塊的位置、旋轉和縮放轉換。 |
| **處理子物件** | `if sub_entity_type == 'LINE': ... process_line(sub_entity, f)` | 遍歷拆解後的**子物件**，根據其類型（`LINE`, `LWPOLYLINE` 等）呼叫對應的 `process_...` 函式進行處理。 |
| **處理一般實體** | `elif entity_type == 'LINE': ... process_line(entity, f)` | 如果頂層物件**不是** `INSERT`（只是一般的 LINE、TEXT 等），則直接呼叫相應的 `process_...` 函式。 |
| **匯總統計** | `print(f"提取的總線段數: {total_segments}")` | 處理完畢後，印出該檔案的處理摘要。|

-----

### 函式拆解：`batch_process_folder`

**批次處理的啟動器**。遍歷指定資料夾中的所有 `.dxf` 檔案，並為每個檔案呼叫 `extract_dxf_data`。

```python
def batch_process_folder(input_folder: str, output_folder: str):
```

| 參數 | 類型 | 說明 |
| :--- | :--- | :--- |
| `input_folder` | `str` | 包含所有來源 `.dxf` 檔案的資料夾路徑。 |
| `output_folder` | `str` | 用於儲存所有輸出 `.csv` 檔案的資料夾路徑。 |
| **回傳** | `None` | |

#### 程式解析

| 行為 | 程式碼片段 | 說明 |
| :--- | :--- | :--- |
| **確保輸出資料夾** | `os.makedirs(output_folder, exist_ok=True)` | 確保輸出資料夾存在。 |
| **篩選 DXF 檔案** | `all_files = os.listdir(input_folder)`<br>`dxf_files = [f for f in all_files if f.lower().endswith('.dxf')]` | 讀取並篩選出副檔名為 `.dxf` 的檔案。 |
| **建立輸出路徑** | `base_name = os.path.splitext(filename)[0]`<br>`output_filename = f"{base_name}.csv"` | 根據輸入的 DXF 檔名（例如 `drawing.dxf`），生成對應的 CSV 輸出檔名（例如 `drawing.csv`）。 |
| **呼叫核心函式** | `extract_dxf_data(full_dxf_path, full_output_csv_path)` | 在迴圈中，為每個 DXF 檔案呼叫 `extract_dxf_data` 函式，傳入完整的輸入和輸出路徑，以執行單一檔案的數據提取。 |
| **批次完成** | `print(f"成功處理 {total_files_processed} / {len(dxf_files)} 個檔案。")` | 所有檔案處理完畢後，印出最終的批次處理匯總報告。 |