# `color` feature 設計メモ (2026-03-04)

## 要求仕様の整理

1. `--features color` コンパイル時、`Shape` に `pub colormap: HashMap<TShapeId, Rgb>` が追加される
2. `clean()` / Boolean 演算でカラーマップが保持される
3. STEP 読み込み/書き込みでカラーが保持される
   - 形全体への色はすべての面への色へ変換する
4. ライブラリ使用者が `colormap` を参照して gltf 出力などを書けるようにする

---

## 1. 新しい Rust 型

### `Rgb` 構造体

```rust
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rgb {
    pub r: f32,  // 0.0..=1.0
    pub g: f32,
    pub b: f32,
}
```

### `TShapeId` ―― colormap のキー

OCCT では `TopoDS_Shape` が `Handle<TopoDS_TShape>` を保持しており、
ポインタ値がトポロジーの同一性を表す（`Handle::get()` が返す生ポインタ）。

```rust
/// `TopoDS_TShape*` のアドレス値。Shape が生きている間だけ有効。
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TShapeId(u64);
```

C++ 側から取得する補助 FFI 関数：

```cpp
// wrapper.cpp
uint64_t face_tshape_id(const TopoDS_Face& face) {
    return reinterpret_cast<uint64_t>(face.TShape().get());
}
```

### `Shape` 構造体の変更

```rust
pub struct Shape {
    pub(crate) inner: cxx::UniquePtr<ffi::TopoDS_Shape>,
    #[cfg(feature = "color")]
    pub colormap: std::collections::HashMap<TShapeId, Rgb>,
}
```

`color` feature が無効のとき、バイナリサイズ・ABI はまったく変わらない。

---

## 2. STEP カラー読み込み

### 現状の問題

現在の `read_step_stream` は `STEPControl_Reader` を使用しており、
色情報（`STYLED_ITEM` / `COLOUR_RGB`）を無視する。

### 解決策：XDE（Extended Data Framework）の利用

`STEPCAFControl_Reader` を使うと STEP 内の色属性を
`XCAFDoc_ColorTool` 経由で取得できる。

```cpp
// color feature 時の実装骨格
std::unique_ptr<ShapeWithColors> read_step_stream_with_colors(RustReader& reader) {
    RustReadStreambuf sbuf(reader);
    std::istream is(&sbuf);

    auto* reader_ptr = new STEPCAFControl_Reader();
    reader_ptr->SetColorMode(true);
    if (reader_ptr->ReadStream("stream", is) != IFSelect_RetDone)
        return nullptr;

    Handle(TDocStd_Document) doc;
    auto app = XCAFApp_Application::GetApplication();
    app->NewDocument("MDTV-XCAF", doc);
    reader_ptr->Transfer(doc);

    Handle(XCAFDoc_ShapeTool) shape_tool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
    Handle(XCAFDoc_ColorTool) color_tool = XCAFDoc_DocumentTool::ColorTool(doc->Main());

    // ルートシェイプを取得
    TDF_LabelSequence roots;
    shape_tool->GetFreeShapes(roots);
    TopoDS_Shape root_shape = shape_tool->GetShape(roots.First());

    auto result = std::make_unique<ShapeWithColors>();
    result->shape = root_shape;

    // 全フェイスに色を割り当て（形全体への色もフェイスへ展開）
    for (TopExp_Explorer ex(root_shape, TopAbs_FACE); ex.More(); ex.Next()) {
        const TopoDS_Face& face = TopoDS::Face(ex.Current());
        TDF_Label face_label;

        Quantity_ColorRGBA color;
        bool found = false;

        // フェイスに直接色があるか確認
        if (shape_tool->FindShape(face, face_label)) {
            found = color_tool->GetColor(face_label, XCAFDoc_ColorSurf, color);
            if (!found) found = color_tool->GetColor(face_label, XCAFDoc_ColorGen, color);
        }

        // 親（ソリッド/シェル/コンパウンド）に色があれば継承
        if (!found) {
            // 親ラベルをたどって色を探す
            // ... (省略: 親ラベル探索ロジック)
        }

        if (found) {
            uint64_t id = reinterpret_cast<uint64_t>(face.TShape().get());
            result->face_ids.push_back(id);
            result->colors.push_back(color.GetRGB().Red());
            result->colors.push_back(color.GetRGB().Green());
            result->colors.push_back(color.GetRGB().Blue());
        }
    }

    return result;
}
```

`ShapeWithColors` は C++ 側の構造体で cxx bridge を通じて返す：

```rust
// ffi.rs
struct ShapeColorData {
    face_ids: Vec<u64>,   // TShapeId 値（3要素ずつ colors と対応）
    colors: Vec<f32>,     // [r0, g0, b0, r1, g1, b1, ...]
}
```

Rust 側でこれを `HashMap<TShapeId, Rgb>` に変換してから `Shape.colormap` に格納する。

### 必要な追加 OCC ライブラリ

| ライブラリ | 用途 |
|-----------|------|
| `TKLCAF`  | OCAF ラベル基盤（`TDF_Label` 等） |
| `TKXCAF`  | `XCAFDoc_ColorTool`, `XCAFDoc_ShapeTool` |
| `TKCDF`   | OCAF ドキュメント管理 |
| `TKCAF`   | OCAF セッション |
| `TKBinXDE`| (オプション) XDE バイナリシリアライズ |

`build.rs` の `OCC_LIBS` に `color` feature 時だけ追加する：

```rust
#[cfg(feature = "color")]
const COLOR_LIBS: &[&str] = &["TKLCAF", "TKXCAF", "TKCDF", "TKCAF"];
```

ただし `bundled` feature で使う CMake オプションに
`BUILD_MODULE_ApplicationFramework=ON` の追加が必要（現在は OFF）。
これによりビルド時間が大幅に増加する可能性がある。

---

## 3. STEP カラー書き込み

```cpp
bool write_step_stream_with_colors(
    const TopoDS_Shape& shape,
    const rust::Vec<uint64_t>& face_ids,
    const rust::Vec<float>& colors,
    RustWriter& writer)
{
    Handle(TDocStd_Document) doc;
    auto app = XCAFApp_Application::GetApplication();
    app->NewDocument("MDTV-XCAF", doc);

    Handle(XCAFDoc_ShapeTool) shape_tool = XCAFDoc_DocumentTool::ShapeTool(doc->Main());
    Handle(XCAFDoc_ColorTool) color_tool = XCAFDoc_DocumentTool::ColorTool(doc->Main());

    TDF_Label root_label = shape_tool->AddShape(shape);

    // カラーマップを構築
    std::unordered_map<uint64_t, std::array<float, 3>> cmap;
    for (size_t i = 0; i < face_ids.size(); ++i) {
        cmap[face_ids[i]] = {colors[3*i], colors[3*i+1], colors[3*i+2]};
    }

    // 各フェイスに色を設定
    for (TopExp_Explorer ex(shape, TopAbs_FACE); ex.More(); ex.Next()) {
        const TopoDS_Face& face = TopoDS::Face(ex.Current());
        uint64_t id = reinterpret_cast<uint64_t>(face.TShape().get());
        auto it = cmap.find(id);
        if (it != cmap.end()) {
            TDF_Label face_label = shape_tool->AddShape(face);
            Quantity_Color color(it->second[0], it->second[1], it->second[2],
                                 Quantity_TOC_sRGB);
            color_tool->SetColor(face_label, color, XCAFDoc_ColorSurf);
        }
    }

    STEPCAFControl_Writer writer_obj;
    writer_obj.SetColorMode(true);
    writer_obj.Transfer(doc);

    RustWriteStreambuf sbuf(writer);
    std::ostream os(&sbuf);
    writer_obj.WriteStream(os);
    return true;
}
```

Rust 側の公開 API：

```rust
impl Shape {
    #[cfg(feature = "color")]
    pub fn write_step_with_colors(&self, writer: &mut impl Write) -> Result<(), Error> {
        let (ids, colors) = colormap_to_vecs(&self.colormap);
        // FFI 呼び出し
        ...
    }
}
```

---

## 4. Boolean 演算でのカラーマップ保持

### 課題：deep copy が TShape* を破壊する

現在の実装では `BRepAlgoAPI_Fuse/Cut/Common` の直後に
`BRepBuilderAPI_Copy` で deep copy する（Bug 1 対応）。
deep copy は新しい `TopoDS_TShape` オブジェクトを割り当てるため、
入力シェイプの `TShape*` と結果シェイプの `TShape*` が一致しない。

カラー引き継ぎには「どの入力フェイスが結果のどのフェイスになったか」の
マッピングが必要。

### `#ifdef` は不要：1 関数 + Rust 側 `#[cfg]` のみ

「`subtract` と `subtract_colored` の 2 関数を `#ifdef` で切り替える」案は
**不要**。より良い方法がある。

**設計方針：C++ は常にマッピングを返す 1 関数。Rust 側 `#[cfg]` だけで colormap の使/不使を制御する。**

- C++ に `#ifdef` は一切書かない
- FFI の関数も 1 種類（`boolean_cut` など）のまま
- `shape.rs` 側の `#[cfg(feature = "color")]` フィールドへの代入だけが条件分岐

```
subtract(a, b)  ←  Rust から呼ぶのはこれ 1 つだけ（color の有無によらず）
     │
     └─ C++: BooleanShapeResult { shape, new_faces, from_a[], from_b[] } を常に返す
```

マッピング収集のオーバーヘッドは Boolean 演算本体（数百 ms）に比べて無視できる。

### マッピング構築アルゴリズム（C++ 側、1 関数に統合）

```
① op.Build()
② deep copy 前に面マッピングを収集（A と B 両方）:
   - fa ∈ A: op.Modified(fa) が非空 → 結果フェイスが fa から派生
   - fa ∈ A: op.Modified(fa) が空 かつ !op.IsDeleted(fa) → 結果に fa がそのまま残存
   - fb ∈ B: 同様
③ deep copy 実行 (BRepBuilderAPI_Copy copier)
④ copier.ModifiedShape(pre_copy_face) → post_copy_face の対応を構築
⑤ ② と ④ を合成:
   post_copy_face.TShape* → pre_copy_face.TShape* → input_face.TShape*
```

C++ の返却構造体（cxx bridge 共有型）：

```rust
// ffi.rs の shared struct — color feature の有無に関係なく常に定義
struct BooleanResult {
    // shape と new_faces の TShape* は deep copy 後の値
    /// pairs: [new_tshape_id, input_a_tshape_id, ...]
    from_a: Vec<u64>,
    /// pairs: [new_tshape_id, input_b_tshape_id, ...]
    from_b: Vec<u64>,
}
// UniquePtr<BooleanShape> は既存のまま（shape + new_faces）
// BooleanResult は add-on として別途返すか、BooleanShape に埋め込む
```

Rust 側での colormap 合成（`#[cfg]` で囲むのはこの呼び出し箇所だけ）：

```rust
// shape.rs
impl Shape {
    pub fn subtract(&self, other: &Shape) -> Result<BooleanShape, Error> {
        let r = ffi::boolean_cut(&self.inner, &other.inner)?; // 戻り値に from_a/from_b 含む
        Ok(BooleanShape {
            shape: Shape {
                inner: ffi::result_shape(&r),
                #[cfg(feature = "color")]
                colormap: merge_colormaps(&r.from_a, &r.from_b,
                                          &self.colormap, &other.colormap),
            },
            new_faces: Shape {
                inner: ffi::result_new_faces(&r),
                #[cfg(feature = "color")]
                colormap: merge_new_faces_colormaps(&r.from_b, &other.colormap),
            },
        })
    }
}
```

```rust
#[cfg(feature = "color")]
fn merge_colormaps(
    from_a: &[u64],
    from_b: &[u64],
    colormap_a: &HashMap<TShapeId, Rgb>,
    colormap_b: &HashMap<TShapeId, Rgb>,
) -> HashMap<TShapeId, Rgb> {
    let mut result = HashMap::new();
    for pair in from_a.chunks(2) {
        let (new_id, old_id) = (TShapeId(pair[0]), TShapeId(pair[1]));
        if let Some(&color) = colormap_a.get(&old_id) {
            result.insert(new_id, color);
        }
    }
    for pair in from_b.chunks(2) {
        let (new_id, old_id) = (TShapeId(pair[0]), TShapeId(pair[1]));
        if let Some(&color) = colormap_b.get(&old_id) {
            result.insert(new_id, color);
        }
    }
    result
}
```

### C++ 実装の変更量

現在の `boolean_fuse` / `boolean_cut` / `boolean_common` を
マッピング収集込みの実装に **置き換える**（各関数 +30〜50 行程度）。
既存の関数が 1 本のまま維持され、`#ifdef` による分岐は生まれない。

---

## 5. `clean()` でのカラーマップ保持

### 課題：face マージによる TShape* の消滅

`ShapeUpgrade_UnifySameDomain` は同一ドメインのフェイスを統合するため、
複数の入力フェイスが 1 つの出力フェイスに合流する。

### 解決策：BRepTools_History の利用

OCCT 7.8+ では `ShapeUpgrade_UnifySameDomain::History()` が
`Handle(BRepTools_History)` を返す。

```cpp
ShapeUpgrade_UnifySameDomain unifier(shape, true, true, false);
unifier.Build();
auto hist = unifier.History();

// 各出力フェイスに対して由来の入力フェイスを収集
for (TopExp_Explorer ex(unifier.Shape(), TopAbs_FACE); ex.More(); ex.Next()) {
    const TopoDS_Shape& out_face = ex.Current();
    // hist->IsRemoved(in_face) / hist->Modified(in_face) / hist->Generated(in_face)
}
```

### カラー統合ポリシー

複数の異なる色を持つフェイスが 1 つにマージされた場合：

| ポリシー | 内容 | 採用判断 |
|---------|------|---------|
| first-wins | 最初に見つかった色を使う | シンプル。実装コスト最小 |
| average | RGB の平均値 | 自然な結果。計算コスト無視できる |
| none | 色なしにする（エントリ削除） | 最も保守的 |

**推奨：first-wins**（シンプルさ優先。ユーザーが必要なら再着色できる）

---

## 6. 依存ライブラリ追加の影響

### ビルド時間

現在 `bundled` feature では OCCT を CMake でビルドする際に
`BUILD_MODULE_ApplicationFramework=OFF` と設定して OCAF 関連を除外している。
`TKXCAF` 等のために `ApplicationFramework` を有効化すると：

- **ビルド時間**: 추加で数分（OCAF ソースが増加）
- **ライブラリサイズ**: 数 MB 増加

この影響は `color` feature 非使用ユーザーに及ばないよう、
CMake 引数を feature 条件分岐する必要がある。

```rust
// build.rs
if cfg!(feature = "color") {
    cmake_config.define("BUILD_MODULE_ApplicationFramework", "ON");
} else {
    cmake_config.define("BUILD_MODULE_ApplicationFramework", "OFF");
}
```

### prebuilt feature

`OCCT_ROOT` のシステム OCCT がこれらのライブラリを含まない場合がある。
ドキュメントに明記が必要。

---

## 7. 公開 API 設計

```rust
// カラーマップの利用例（ライブラリ使用者が書く gltf 出力）
#[cfg(feature = "color")]
fn export_gltf(shape: &Shape, mesh: &Mesh) {
    // 各フェイスの色を TShapeId で引く
    for face in shape.faces() {
        let id = face.tshape_id();      // 新規追加 API
        if let Some(rgb) = shape.colormap.get(&id) {
            // gltf material 設定
        }
    }
}
```

追加が必要なメソッド：

```rust
impl Face {
    #[cfg(feature = "color")]
    pub fn tshape_id(&self) -> TShapeId {
        TShapeId(ffi::face_tshape_id(&self.inner))
    }
}
```

---

## 8. 実装難易度まとめ

| コンポーネント | 難易度 | 主な作業 |
|--------------|--------|---------|
| `Rgb` / `TShapeId` 型定義 | ★☆☆ | Rust のみ |
| `color` feature フラグ + Shape フィールド | ★☆☆ | Cargo.toml + shape.rs |
| `face_tshape_id` FFI | ★☆☆ | C++ 1 関数 |
| `Face::tshape_id()` | ★☆☆ | Rust メソッド 1 つ |
| STEP 読み込み（色付き） | ★★★ | 新 C++ クラス + OCC ライブラリ追加 + CMake 変更 |
| STEP 書き込み（色付き） | ★★☆ | 新 C++ 関数 + XDE ドキュメント操作 |
| Boolean 演算カラー保持 | ★★★ | 3 演算 × C++ マッピング収集（各 50 行） |
| `clean()` カラー保持 | ★★☆ | History API + Rust 側マージ処理 |
| build.rs CMake 分岐 | ★★☆ | feature 条件分岐 + ライブラリリスト拡張 |
| **合計** | **中〜高** | **推定 1〜2 週間** |

---

## 9. 実装推奨順序

1. **Rgb / TShapeId / colormap フィールド追加**（★☆☆）
   - コンパイルが通る最小変更。他の作業の足場。

2. **`face_tshape_id` FFI + `Face::tshape_id()`**（★☆☆）
   - ライブラリ使用者が colormap を参照できるようになる。

3. **Boolean 演算カラー保持**（★★★）
   - ユーティリティとしての価値が高い。テストが書きやすい。

4. **`clean()` カラー保持**（★★☆）
   - Boolean 演算の実装で得た知見が転用できる。

5. **STEP 書き込み**（★★☆）
   - XDE の基本操作を学ぶ入り口。

6. **STEP 読み込み + CMake 変更**（★★★）
   - 最も影響範囲が広い（ビルド時間増加）。最後に。

---

## 10. 代替案：色を C++ 側で保持する

`TShape*` を Rust の HashMap キーとして使う代わりに、
OCCT の `TDF_Attribute` 機構（OCAF）で色を shape に付ける方法もある。

| | Rust 側 HashMap | OCAF Attribute |
|---|---|---|
| 実装複雑度 | 低（上記方針） | 高（Document 管理が必要） |
| STEP I/O との親和性 | 変換が必要 | 自然（XDE が直接扱う） |
| Boolean 演算後の保持 | マッピングロジックが必要 | OCAF History で自動化の余地 |
| 追加ライブラリ | color feature 時のみ | color feature 時のみ（同じ） |

**推奨：Rust 側 HashMap**（シンプルさ優先、OCAF 導入コストを避ける）

---

## 11. 結論

---

## 12. 修正案1：Boolean 演算カラー保持（raw 返し + Rust 側 TShape* 照合）

セクション 4 の設計（C++ 側でマッピングを構築して返す）に対する改善案。

### 方針

`boolean_fuse` 等が **deep copy なしの raw `BooleanShape`** を返す。
Rust には `a`（self）・`b`（other）・`raw_bs` の 3 つが同時に生存しており、
OCCT が変化しなかったフェイスの `TShape*` を保持することを利用して
**Rust 側で TShape* 照合**を行う。
ユーザーに返す直前に Rust から deep copy を呼び、メモリ安全性を担保する。

新しい opaque 型（`BooleanOp`、`DeepCopyResult`）は**一切不要**。

### TShape* 照合の原理

```
boolean_fuse 実行前:
  a のフェイス  TShape* = 0xAAAA
  b のフェイス  TShape* = 0xBBBB

boolean_fuse 後（raw、変化なしのフェイスは同一 TShape* を保持）:
  raw_bs のフェイス TShape* = 0xAAAA  ← a と一致 → a の色を引き継げる
  raw_bs のフェイス TShape* = 0xBBBB  ← b と一致 → b の色を引き継げる
  raw_bs のフェイス TShape* = 0xCCCC  ← 新規（交差で生成）→ 色なし
```

`BRepAlgoAPI::Modified()` を使わなくてよいため、
演算オブジェクトを Rust に見せる必要がない。

### トレードオフ

| フェイスの種類 | TShape* | 色の扱い |
|---|---|---|
| 変化なし（交差から遠い面） | a/b と一致 | 引き継がれる ✓ |
| トリムされた面（交差境界） | 新しい TShape* | 失われる |
| 断面フェイス（cut の切り口） | 新しい TShape* | 失われる（もともと色なし） |

多くの実用的な CAD モデルでは交差境界付近のみが変化するため、
大部分のフェイスカラーが保持される。

### 追加する FFI（新しい型なし）

```rust
// ffi.rs に追加する関数 2 つのみ

// ① raw シェイプ内の全フェイスの TShape* ID 一覧
fn face_ids_in_shape(shape: &TopoDS_Shape) -> Vec<u64>;

// ② deep copy + pre→post ID マッピング（out パラメータで型追加を回避）
fn deep_copy_with_id_map(
    shape: &TopoDS_Shape,
    id_map: &mut Vec<u64>,  // C++ 側: rust::Vec<uint64_t>& [post_id, pre_id, ...]
) -> UniquePtr<TopoDS_Shape>;
```

C++ 実装：

```cpp
// face_ids_in_shape
rust::Vec<uint64_t> face_ids_in_shape(const TopoDS_Shape& shape) {
    rust::Vec<uint64_t> ids;
    for (TopExp_Explorer ex(shape, TopAbs_FACE); ex.More(); ex.Next())
        ids.push_back(reinterpret_cast<uint64_t>(ex.Current().TShape().get()));
    return ids;
}

// deep_copy_with_id_map
std::unique_ptr<TopoDS_Shape> deep_copy_with_id_map(
    const TopoDS_Shape& input,
    rust::Vec<uint64_t>& id_map)
{
    BRepBuilderAPI_Copy copier(input, Standard_True, Standard_False);
    for (TopExp_Explorer ex(input, TopAbs_FACE); ex.More(); ex.Next()) {
        const auto& pre  = ex.Current();
        const auto& post = copier.ModifiedShape(pre);
        id_map.push_back(reinterpret_cast<uint64_t>(post.TShape().get()));
        id_map.push_back(reinterpret_cast<uint64_t>(pre .TShape().get()));
    }
    return std::make_unique<TopoDS_Shape>(copier.Shape());
}
```

既存の `boolean_fuse` / `boolean_cut` / `boolean_common` から
**`BRepBuilderAPI_Copy` を削除**して raw 結果を返すように変更する。

### Rust 側の flow（union の例）

```rust
pub fn union(&self, other: &Shape) -> Result<BooleanShape, Error> {
    // ① raw 結果（Handle 共有、deep copy なし）
    let raw = ffi::boolean_fuse(&self.inner, &other.inner);
    if raw.is_null() { return Err(Error::BooleanOperationFailed); }
    let raw_shape    = ffi::boolean_shape_shape(&raw);
    let raw_newfaces = ffi::boolean_shape_new_faces(&raw);
    // この時点で self・other・raw が同時に生存 → Handle 共有は安全

    // ② TShape* 照合でカラー引き継ぎ（color feature のみ）
    #[cfg(feature = "color")]
    let pre_cmap = {
        let raw_ids: HashSet<u64> =
            ffi::face_ids_in_shape(&raw_shape).into_iter().collect();
        let mut m = HashMap::new();
        for (&id, &c) in self.colormap.iter().chain(other.colormap.iter()) {
            if raw_ids.contains(&id.0) { m.insert(id, c); }
        }
        m
    };

    // ③ deep copy（self/other はまだ借用中 → Handle 共有でも安全）
    let mut id_map_s  = Vec::new();
    let mut id_map_nf = Vec::new();
    let copied_shape    = ffi::deep_copy_with_id_map(&raw_shape,    &mut id_map_s)?;
    let copied_newfaces = ffi::deep_copy_with_id_map(&raw_newfaces, &mut id_map_nf)?;
    drop(raw); // raw の Handle 共有を解放（self/other より先に drop しても安全）

    // ④ pre→post ID 付け替え（color feature のみ）
    #[cfg(feature = "color")]
    let colormap = id_map_s.chunks(2)
        .filter_map(|p| pre_cmap.get(&TShapeId(p[1])).map(|&c| (TShapeId(p[0]), c)))
        .collect();

    Ok(BooleanShape {
        shape: Shape {
            inner: copied_shape,
            #[cfg(feature = "color")] colormap,
        },
        new_faces: Shape {
            inner: copied_newfaces,
            #[cfg(feature = "color")] colormap: HashMap::new(), // 断面は色なし
        },
    })
}
```

### セクション 4 との比較

| | セクション 4（C++ マッピング） | 修正案1（Rust TShape* 照合） |
|---|---|---|
| 新しい FFI 型 | なし | なし（同じ） |
| 新しい C++ 関数 | boolean_fuse 等を大幅変更 | `face_ids_in_shape` + `deep_copy_with_id_map` の 2 関数のみ |
| 既存 boolean_fuse の変更 | deep copy 削除のみ | 同じ |
| `#ifdef` | 不要 | 不要 |
| トリム面の色 | 保持できる（Modified() 使用） | 失われる |
| 変化なし面の色 | 保持できる | 保持できる |
| 実装複雑度 | ★★★ | ★★☆ |

**修正は可能。** 技術的障壁はなく、OCCT 7.9 の API で完全にサポートされている。

主なリスク：
- `bundled` feature での CMake ビルド時間増加（`ApplicationFramework` 有効化）
- Boolean 演算のフェイスマッピングロジックのテストコストが高い（
  3 演算 × 2 入力シェイプ × deep copy の 2 段合成が必要）

上記実装推奨順序に従えば、カラーマップ参照（ステップ 1〜2）は
STEP I/O（ステップ 5〜6）に先行して利用可能になるため、
ライブラリ使用者はカラー情報を手動で設定して gltf 出力を試すことができる。

---

## 13. 修正案2：Boolean 演算カラー保持（C++ が from_a/from_b を構築、Rust が色に反映）

修正案1（Rust 側 TShape* 照合）の問題点「トリムされた面の色が失われる」を解消する案。
C++ が `Modified()` を op 生存中に呼んで from_a/from_b マッピングを構築し、
Rust 側がそれを使って colormap を合成する。

### 方針

- `boolean_cut` / `boolean_fuse` / `boolean_common` の 3 関数を変更し、
  既存の `BooleanShape` opaque 型に `from_a` / `from_b` フィールドを追加する。
- deep copy は C++ 関数内で完結させる（修正案1 の「Rust から deep copy を呼ぶ」不要）。
- C++ に `#ifdef` は書かない。Rust 側の `#[cfg(feature = "color")]` だけで分岐する。

### C++ 側の BooleanShape 構造体変更

```cpp
// wrapper.cpp（既存の BooleanShape クラスに 2 フィールドを追加）
class BooleanShape {
public:
    TopoDS_Shape shape;                   // 既存（deep copy 済み）
    TopoDS_Shape new_faces;               // 既存（deep copy 済み）
    std::vector<uint64_t> from_a;         // 追加: [post_id, src_a_id, ...]
    std::vector<uint64_t> from_b;         // 追加: [post_id, src_b_id, ...]
};
```

### C++ 関数一覧

| 関数名 | 変更内容 | 役割 |
|---|---|---|
| `boolean_cut(a, b)` | 変更 | `from_a` / `from_b` を構築して格納 |
| `boolean_fuse(a, b)` | 変更 | `from_a` / `from_b` を構築（`from_b` は通常空） |
| `boolean_common(a, b)` | 変更 | `from_a` / `from_b` を構築 |
| `boolean_shape_shape(r)` | 変更なし | `shape` アクセサ |
| `boolean_shape_new_faces(r)` | 変更なし | `new_faces` アクセサ |
| `boolean_shape_from_a(r)` | **新規** | `from_a` を `rust::Vec<uint64_t>` で返す |
| `boolean_shape_from_b(r)` | **新規** | `from_b` を `rust::Vec<uint64_t>` で返す |

### C++ 内部ロジック（boolean_cut の場合）

```cpp
std::unique_ptr<BooleanShape> boolean_cut(
    const TopoDS_Shape& a, const TopoDS_Shape& b)
{
    try {
        // ① Boolean 演算実行
        BRepAlgoAPI_Cut op(a, b);
        op.Build();
        if (!op.IsDone()) return nullptr;

        // ② op 生存中に Modified() / IsDeleted() でフェイスの由来を収集
        //    pre_relay: pre_copy_face.TShape* → src_face.TShape*
        std::unordered_map<uint64_t, uint64_t> pre_relay_a, pre_relay_b;

        auto collect = [&](const TopoDS_Shape& src,
                           std::unordered_map<uint64_t, uint64_t>& relay)
        {
            for (TopExp_Explorer ex(src, TopAbs_FACE); ex.More(); ex.Next()) {
                const auto& sf = ex.Current();
                uint64_t src_id = reinterpret_cast<uint64_t>(sf.TShape().get());
                if (op.IsDeleted(sf)) continue;
                const auto& mods = op.Modified(sf);
                if (mods.IsEmpty()) {
                    // 変化なし: 結果に同一 TShape* で残存
                    relay[src_id] = src_id;
                } else {
                    for (auto it = mods.begin(); it != mods.end(); ++it) {
                        uint64_t pre_id = reinterpret_cast<uint64_t>(it->TShape().get());
                        relay[pre_id] = src_id;
                    }
                }
            }
        };
        collect(a, pre_relay_a);
        collect(b, pre_relay_b);

        // ③ deep copy（Bug 1 対応）
        BRepBuilderAPI_Copy copier(op.Shape(), Standard_True, Standard_False);

        // ④ TopTools_IndexedMapOfShape でインデックス一致により pre→post ID 変換
        //    BRepBuilderAPI_Copy はフェイス順序を保持するため、同インデックスが対応
        TopTools_IndexedMapOfShape pre_map, post_map;
        TopExp::MapShapes(op.Shape(),      TopAbs_FACE, pre_map);
        TopExp::MapShapes(copier.Shape(),  TopAbs_FACE, post_map);

        // ⑤ from_a / from_b に [post_id, src_id] ペアを格納
        auto result = std::make_unique<BooleanShape>();
        result->shape = copier.Shape();

        // new_faces の deep copy は既存ロジックを流用
        // ... (省略)

        for (int i = 1; i <= pre_map.Size(); ++i) {
            uint64_t pre_id  = reinterpret_cast<uint64_t>(pre_map(i).TShape().get());
            uint64_t post_id = reinterpret_cast<uint64_t>(post_map(i).TShape().get());
            auto it_a = pre_relay_a.find(pre_id);
            if (it_a != pre_relay_a.end()) {
                result->from_a.push_back(post_id);
                result->from_a.push_back(it_a->second);
            }
            auto it_b = pre_relay_b.find(pre_id);
            if (it_b != pre_relay_b.end()) {
                result->from_b.push_back(post_id);
                result->from_b.push_back(it_b->second);
            }
        }
        return result;
    } catch (const Standard_Failure&) { return nullptr; }
}

// アクセサ（cxx bridge はフィールド直接アクセス不可）
rust::Vec<uint64_t> boolean_shape_from_a(const BooleanShape& r) {
    rust::Vec<uint64_t> v;
    for (auto x : r.from_a) v.push_back(x);
    return v;
}
rust::Vec<uint64_t> boolean_shape_from_b(const BooleanShape& r) {
    rust::Vec<uint64_t> v;
    for (auto x : r.from_b) v.push_back(x);
    return v;
}
```

### Rust 側の責任

```rust
// shape.rs
impl Shape {
    pub fn subtract(&self, other: &Shape) -> Result<BooleanShape, Error> {
        let bs = ffi::boolean_cut(&self.inner, &other.inner);
        if bs.is_null() { return Err(Error::BooleanOperationFailed); }

        #[cfg(feature = "color")]
        let colormap = {
            let from_a = ffi::boolean_shape_from_a(&bs);
            let from_b = ffi::boolean_shape_from_b(&bs);
            from_a.chunks(2)
                .filter_map(|p| self.colormap.get(&TShapeId(p[1])).map(|&c| (TShapeId(p[0]), c)))
                .chain(
                    from_b.chunks(2)
                        .filter_map(|p| other.colormap.get(&TShapeId(p[1])).map(|&c| (TShapeId(p[0]), c)))
                )
                .collect()
        };

        Ok(BooleanShape {
            shape: Shape {
                inner: ffi::boolean_shape_shape(&bs),
                #[cfg(feature = "color")] colormap,
            },
            new_faces: Shape {
                inner: ffi::boolean_shape_new_faces(&bs),
                #[cfg(feature = "color")] colormap: std::collections::HashMap::new(),
            },
        })
    }
}
```

### 修正案1 との比較

| | 修正案1（Rust TShape* 照合） | 修正案2（C++ Modified() 使用） |
|---|---|---|
| トリムされた面の色 | 失われる | **保持される** |
| 変化なし面の色 | 保持される | 保持される |
| 新しい FFI 型 | なし | なし（同じ） |
| 新しい C++ 関数 | `face_ids_in_shape` + `deep_copy_with_id_map` | `boolean_shape_from_a` + `boolean_shape_from_b` |
| 既存 boolean_fuse 等の変更 | deep copy 削除のみ | **from_a/from_b 収集ロジック追加（各 +50 行程度）** |
| `#ifdef` | 不要 | 不要 |
| Rust 側の deep copy 呼び出し | 必要（複雑） | 不要（C++ 内で完結） |
| 実装複雑度 | ★★☆ | ★★★ |

### 採用判断

修正案2 はトリム面の色も保持できる点でセクション 4 の設計と同等の精度を持つ。
C++ 関数内での責任が完結しており、Rust 側は `from_a` / `from_b` を受け取って
colormap に反映するだけでよい。
セクション 4 との違いは「BooleanResult を別型で返す」か
「BooleanShape に from_a/from_b を埋め込む」かの設計差のみ。
既存の `boolean_shape_shape` / `boolean_shape_new_faces` アクセサが変わらない点で
後方互換性が高い。
