# chijin 実装メモ (2026-03-02)

OCCT 7.9.3 を Rust から cxx クレートで呼び出す際に判明した注意事項をまとめる。

---

## 1. BinTools バイナリ形式はシーク可能なストリームが必要

### 問題

`BinTools::Read` に非シーク型の `std::istream` (例: 独自 `std::streambuf`) を渡すと
`Standard_Failure` が送出されてクラッシュする。

### 原因

OCCT 7.7 以降の `BinTools` バイナリ形式は、共有サブシェイプ (ボックスの頂点など複数エッジが共有する) の重複書き込みを避けるため**後方参照 (backward reference)** を使用する。
内部の `BinTools_IStream` がバイトオフセットで `seekg()` を呼び出すため、
シーク不能なストリームでは読み取りが失敗する。

テキスト形式 (`BRepTools`) は連番 ID で参照するため同じ制約はない。

### 対処

読み取り側で Rust の reader から全バイトをメモリに読み込み、
シーク可能な `std::istringstream` 経由で `BinTools::Read` に渡す。

```cpp
std::unique_ptr<TopoDS_Shape> read_brep_bin_stream(RustReader& reader) {
    std::string data;
    char buf[8192];
    for (;;) {
        rust::Slice<uint8_t> slice(reinterpret_cast<uint8_t*>(buf), sizeof(buf));
        size_t n = rust_reader_read(reader, slice);
        if (n == 0) break;
        data.append(buf, n);
    }
    std::istringstream iss(std::move(data));
    auto shape = std::make_unique<TopoDS_Shape>();
    BinTools::Read(*shape, iss);   // iss はシーク可能
    ...
}
```

書き込み側 (`BinTools_OStream`) は `myPosition` を内部でインクリメント追跡するため
`seekp()` を呼ばない。独自 `std::streambuf` への書き込みはそのまま使用できる。

---

## 2. OCCT 例外は FFI 境界を越える前に必ず捕捉する

### 問題

`Standard_Failure` (OCCT の例外基底クラス) が cxx の FFI 境界を越えて
Rust 側に到達すると `STATUS_STACK_BUFFER_OVERRUN` (0xc0000409) で即時終了する。

### 原因

`Standard_Failure` は `std::exception` を継承していない。
cxx はデフォルトで `std::exception` 系の例外のみ変換し、それ以外は UB となる。

### 対処

I/O 関数 (`BinTools::Read/Write`, `BRepTools::Read/Write`) をすべて
try/catch で囲む。

```cpp
try {
    BinTools::Read(*shape, iss);
} catch (const Standard_Failure&) {
    return nullptr;
}
```

---

## 3. `TopExp_Explorer` でエッジを列挙すると重複が発生する

### 問題

`TopExp_Explorer(shape, TopAbs_EDGE)` で列挙すると、ボックスの 12 エッジが
24 個 (2 倍) になる。

### 原因

`TopExp_Explorer` は隣接フェースを経由してエッジを巡回する。
共有エッジは隣接フェース数だけ (ボックスなら 2 回) 現れる。

### 対処

`TopTools_IndexedMapOfShape` でユニークなエッジを収集してから `TopoDS_Compound` に
詰め直し、その Compound を Explorer で巡回する。

```cpp
TopTools_IndexedMapOfShape edgeMap;
TopExp::MapShapes(shape, TopAbs_EDGE, edgeMap);
TopoDS_Compound compound;
BRep_Builder builder;
builder.MakeCompound(compound);
for (int i = 1; i <= edgeMap.Extent(); i++) {
    builder.Add(compound, edgeMap(i));
}
return std::make_unique<TopExp_Explorer>(compound, TopAbs_EDGE);
```

---

## 4. `-DOCCT_UWP` で advapi32 依存を排除できる

### 問題

OCCT の `TKernel` は `OSD_WNT.cxx` にプログラム起動時に実行される静的イニシャライザ
(`Init_OSD_WNT`) を持ち、`AllocateAndInitializeSid` (advapi32) を無条件に呼び出す。
Rust は `-nodefaultlibs` を渡すため GCC のスペックファイルが
`-ladvapi32` を追加しなくなり、リンクエラーが発生する。

### 対処

CMake で OCCT をビルドする際に `-DOCCT_UWP` を定義する。
このフラグは `#ifndef OCCT_UWP` で囲まれた Windows セキュリティ API 呼び出し
(`OSD_WNT.cxx`, `OSD_File.cxx`, `OSD_Process.cxx`) をすべて無効化する。

```rust
cmake::Config::new(&source_dir)
    .cflag("-DOCCT_UWP")
    .cxxflag("-DOCCT_UWP")
    // ...
    .build();
```

---

## 5. `Standard_ErrorHandler` の多重定義には `--allow-multiple-definition`

### 問題

MinGW リンカが `Standard_ErrorHandler::Callback::RegisterCallback()` の
multiple definition エラーを出す。

### 原因

`Standard_ErrorHandler.hxx` のインライン定義が `wrapper.o` と `TKernel.a` の両方に
強シンボルとして入る。MinGW はデフォルトでこれをエラーとする。

### 対処

```rust
println!("cargo:rustc-link-arg=-Wl,--allow-multiple-definition");
```

---

## 6. OCCT 7.9 のライブラリ名変更

OCCT 7.8 以降でいくつかのライブラリ名が変わった。

| 旧名 (〜7.7) | 新名 (7.8〜) |
|---|---|
| `TKBinTools` | `TKBin` |
| `TKShapeUpgrade` | `TKShHealing` |
| `TKSTEP`, `TKSTEP209`, `TKSTEPAttr`, `TKSTEPBase` | `TKDESTEP` |
| (新規) | `TKDE`, `TKDECascade` |

---

## 7. cxx ブリッジの namespace とインクルード

cxx が `namespace = "chijin"` を使う場合、C++ 側の型も `chijin::` 名前空間に
属している必要がある。OCCT のグローバル型をブリッジで使うには
`wrapper.h` 内で `using` エイリアスを宣言する。

```cpp
namespace chijin {
    using TopoDS_Shape = ::TopoDS_Shape;
    using TopoDS_Face  = ::TopoDS_Face;
    using TopoDS_Edge  = ::TopoDS_Edge;
    using TopExp_Explorer = ::TopExp_Explorer;
}
```

cxx のインクルードパスは `include!("chijin/cpp/wrapper.h")` のように
クレートルートからの相対パスで指定する。
