# `feature/color2` 実装記録 (2026-03-04)

`feature/color2` ブランチで実施した全作業の記録。
目的・設計判断・試行錯誤の順に記述する。

---

## 1. このブランチの目的

`color` feature（`--features color`）において、Boolean 演算・`clean()`・`translated()`・STEP 読み書きの各操作を通じて face カラーマップが保持されるようにすること。

ブランチ開始時点では：
- `Shape::colormap` フィールドと `TShapeId`/`Rgb` 型は既に存在していた
- Boolean 演算（`fuse`/`cut`/`common`）後の色保持は実装済みだった
- それ以外（`clean`、`translate`、`deep_copy`、STEP I/O）は未実装だった

---

## 2. `clean()` の色保持

### 問題

`Shape::clean()` は `ShapeUpgrade_UnifySameDomain` で同一ドメインの face を統合する。
統合後は TShape* が変わるため、旧 colormap のキーがすべて無効になる。

### 設計

**C++ 側でマッピングを構築、Rust 側で色を引き継ぐ** 方針（Boolean 演算と同方式）。

新しい C++ クラス `CleanShape`：

```cpp
class CleanShape {
public:
    TopoDS_Shape shape;
    std::vector<uint64_t> mapping; // pairs: [new_id, old_id, ...]
};
```

`clean_shape_full()` は `ShapeUpgrade_UnifySameDomain::History()` が返す
`BRepTools_History` を使い、旧 face → 新 face のマッピングを収集する。
`Modified()` が空の face はそのまま残存（`[old_id, old_id]` のペア）。

Rust 側では `entry().or_insert()` により **first-found wins**（最初に見つかった色を採用）。
2 面が統合されたとき厳密な色選択は行わない。

### face_tshape_id の扱い

`face_tshape_id` は `#[cfg(feature = "color")]` でのみ必要な FFI 関数。
`wrapper.h` では `#ifdef CHIJIN_COLOR` ブロックに配置した。

---

## 3. `translated()` と `deep_copy()` の色保持

### 設計の選択肢

| 案 | 方法 | コスト |
|---|---|---|
| C++ 側でマッピング返却 | translate/copy 後の face 対応を C++ が構築 | 操作ごとに C++ 変更が必要 |
| **Rust 側で巡回順 zip（採用）** | `explore_faces` の返却順が保存されると仮定して zip で対応 | C++ 変更ゼロ |

案2を採用した理由：
- 今後、回転・スケールなどの操作が追加されるたびに C++ に変更を加えるコストが高い
- `BRepBuilderAPI_Transform`（translate）と `BRepBuilderAPI_Copy`（deep_copy）はいずれもトポロジー走査順序を保持する
- Boolean/clean は面が増減するため厳密追跡が必要だが、translate/copy は面数が変わらない

実装した `remap_colormap_by_order` ヘルパー（`src/shape.rs`）：

```rust
#[cfg(feature = "color")]
fn remap_colormap_by_order(
    old_inner: &ffi::TopoDS_Shape,
    new_inner: &ffi::TopoDS_Shape,
    old_colormap: &HashMap<TShapeId, Rgb>,
) -> HashMap<TShapeId, Rgb> {
    let old_faces = FaceIterator::new(ffi::explore_faces(old_inner));
    let new_faces = FaceIterator::new(ffi::explore_faces(new_inner));
    old_faces.zip(new_faces)
        .filter_map(|(old, new)| {
            old_colormap.get(&old.tshape_id()).map(|&c| (new.tshape_id(), c))
        })
        .collect()
}
```

---

## 4. `Shape::from_inner` の廃止

### 問題

`Shape::from_inner(inner)` はコンパイルが通るが、colormap が常に空になる「穴」だった。
`--features color` で `Shape` を構成するときに colormap のことを考え忘れやすい。

### 対応

`from_inner` を完全削除。全呼び出し箇所を構造体リテラルに置き換えた：

```rust
Shape {
    inner,
    #[cfg(feature = "color")]
    colormap: std::collections::HashMap::new(),
}
```

`#[cfg]` アトリビュートを明示的に書かなければならないため、
将来の実装者が「colormap の扱いを考える必要がある」ことに気づきやすい設計になった。

---

## 5. 色付き STEP 読み書き

### 使用する OCC ライブラリ

STEP に色属性を読み書きするには XDE（Extended Data Framework）が必要：

| ライブラリ | 用途 |
|-----------|------|
| `TKLCAF`  | `TDocStd_Document` |
| `TKXCAF`  | `XCAFDoc_ColorTool`, `XCAFDoc_ShapeTool`, `XCAFDoc_DocumentTool` |
| `TKCAF`   | `TNaming_NamedShape`（TKXCAF の依存） |
| `TKCDF`   | `CDM_Document`（TKLCAF の依存） |
| `TKDESTEP`| `STEPCAFControl_Reader/Writer`（元の OCC_LIBS に含まれていた） |

### 試行錯誤 1：`XCAFApp_Application` → 可視化ライブラリ依存エラー

設計ドキュメント（`20260304-color機能設計_jp.md`）の骨格コードでは
`XCAFApp_Application::GetApplication()` を使っていた。

しかし実際にリンクすると：

```
undefined reference to 'TPrsStd_DriverTable::Get()'
undefined reference to 'vtable for AIS_Shape'
... (多数)
```

原因は `XCAFApp_Application` が起動時に `XCAFPrs_Driver` を
`TPrsStd_DriverTable` に登録するため、可視化ライブラリ
（`TKXCAFPrs`, `TKTPrsStd`, `TKAIS` 等）に依存すること。
これらは `BUILD_MODULE_Visualization=OFF` でビルドしていないため存在しない。

**解決策**：`XCAFApp_Application` を一切使わず、
`TDocStd_Document` を直接 `new` する：

```cpp
Handle(TDocStd_Document) doc = new TDocStd_Document("XmlXCAF");
```

`TDocStd_Document` は `TKLCAF` のみに依存し、可視化ライブラリに依存しない。

### 試行錯誤 2：XDE ヘッダーを `namespace chijin {}` 内で `#include` → コンパイルエラー

当初、XDE 用の `#include` を `namespace chijin { }` ブロックの中に書いた。
その結果：

```
error: 'is_base_of' is not a member of 'chijin::std'
error: 'hash' is not a class template
```

`namespace chijin { #include <...> }` の形式では、
インクルードされたヘッダー内の `std::` 参照が `chijin::std::` として解決されてしまう。

**解決策**：すべての `#include` を namespace 宣言より前（ファイル先頭付近）に移動。

### 試行錯誤 3：`#ifdef CHIJIN_COLOR` の配置と namespace の閉じ忘れ

ユーザーが `wrapper.cpp` を「`#ifdef CHIJIN_COLOR` が 1 箇所のみ」
という構造に整理した際、namespace の閉じブラケットが `#endif` の外に
取り残されてコンパイルエラーが発生した。

最終的な `wrapper.cpp` の構造：

```
// 共通 #include（namespace 外）

namespace chijin {
    // 共通関数一式（write_step_stream を含む）
} // namespace chijin

#ifdef CHIJIN_COLOR

// XDE 専用 #include（namespace 外）

namespace chijin {
    // read_step_color_stream, write_step_color_stream
    // colored_step_shape/ids/colors_r/g/b
    // clean_shape_full, clean_shape_get, clean_shape_mapping
} // namespace chijin

#endif // CHIJIN_COLOR
```

同様に `wrapper.h` も 2 ブロック構造に整理：

```
namespace chijin { /* 共通宣言 */ } // namespace chijin

#ifdef CHIJIN_COLOR
namespace chijin { /* ColoredStepData, CleanShape, face_tshape_id */ } // namespace chijin
#endif // CHIJIN_COLOR
```

### 色収集ロジック

`read_step_color_stream` 内では `TDF_ChildIterator(doc->Main(), Standard_True)` で
XDE ドキュメントの全ラベルをフラットに走査し、
`ShapeType() == TopAbs_FACE` かつ `XCAFDoc_ColorSurf` または `XCAFDoc_ColorGen` で
色が取れるラベルを colormap に登録する。

### STEP 書き込みロジック

`write_step_color_stream` は：
1. `TDocStd_Document` を直接 `new` してルートシェイプを登録
2. 各 face に対して `FindSubShape` → なければ `AddSubShape` でラベルを取得
3. `SetColor(label, Quantity_Color(..., Quantity_TOC_sRGB), XCAFDoc_ColorSurf)` で色設定
4. `STEPCAFControl_Writer::Transfer(doc)` → `WriteStream(os)` で出力

---

## 6. `build.rs` の整理

### `link_occt_libraries` 関数への切り出し

`main()` はOCCT のパス解決のみに集中し、
リンク・コンパイル設定はすべて `link_occt_libraries(occt_include, occt_lib_dir, color)` に移動。

`color: bool` の値は `cfg!(feature = "color")` の評価結果を呼び出し側（`main`）で渡す。
関数内では単純な `bool` として扱い、`cfg!` マクロに依存しない。

### XDE libs と bundled feature の関係

`BUILD_MODULE_ApplicationFramework=OFF` で CMake ビルドしているが、
`BUILD_MODULE_DataExchange=ON` でビルドされる `TKDESTEP` が XDE に依存しているため、
`TKLCAF` / `TKXCAF` / `TKCAF` / `TKCDF` は DataExchange の依存として実際にはビルドされている。
`color` feature フラグはリンク（と C++ の `#ifdef`）のみを制御する。

---

## 7. テスト構成

### `tests/integration_color.rs`（既存）

- `clean_preserves_face_colors`：clean 後に全 face の色が保持されること
- `clean_merge_preserves_color`：2 face が統合されたとき first-found の色が保持されること
- `colored_box_intersect_z_positive_half_space`：Boolean 演算後の色保持

### `tests/integration_color_step.rs`（新規）

`steps/colored_box.step`（AP214 形式、6 面それぞれ異なる色）を入力として：

| テスト名 | 検証内容 |
|---------|---------|
| `read_colored_step_populates_colormap` | 読み込み後 colormap.len() >= 6、キーがすべて実在 face |
| `write_then_read_preserves_colors` | 書いて読み直しても colormap.len() >= 6 |
| `intersect_colored_step_preserves_colors` | z > 0 半空間との intersect 後も色が残る |
| `translate_colored_step_preserves_colors` | translate 後に全色が保持される |
| `clean_colored_step_preserves_colors` | clean 後に全色が保持される |

出力ファイルは `out/colored_box_*.step` に書き出す。

---

## 8. 残存 warning

スクラッチビルド後に 2 件の未使用警告が残っている：

| 箇所 | 内容 |
|------|------|
| `ffi::clean_shape` | `color` feature では `clean_shape_full` を使うが、非 color 版として宣言は残存 |
| `ffi::face_to_shape` | 現在のテストで使われていない |

どちらも機能上の問題はなく、将来の利用を想定して残している。
