# Shape型の分離設計: Solid + Compound

## 動機

現在の`Shape`は`TopoDS_Shape`の薄いラッパーであり、Solid/Compound/Face/Edge等あらゆるトポロジ型を同一のRust型で表現している。

```rust
pub struct Shape {
    pub(crate) inner: cxx::UniquePtr<ffi::TopoDS_Shape>,
}
```

問題:
- `stretch_vector`にFaceを渡してもコンパイルが通る（実行時にOCCTが失敗するだけ）
- boolean演算の入力がSolidであるべきか、Compoundでもよいか、型から読み取れない
- `new_faces`（Face群）と演算結果（Solid群）が同じ`Shape`型

## 設計方針

COMPSOLIDは無視する。実務上出現せず、OCCT自体もサポートが手薄。

1 Face = 1 Shell = 1 Solid の階層構造を前提とする。

## 新しい型

```rust
/// 単一の閉じた立体。ShapeType == SOLID を構築時に保証。
pub struct Solid {
    inner: UniquePtr<TopoDS_Shape>,
    #[cfg(feature = "color")]
    colormap: HashMap<TShapeId, Rgb>,
}

/// 複数Solidの集合。STEPから読むと基本これ。
pub struct Compound {
    solids: Vec<Solid>,
}

/// Face単体。既存のFace型をそのまま使う。
pub struct Face { .. }  // 既存

/// Face群。boolean演算のnew_faces用。
pub struct FaceGroup {
    faces: Vec<Face>,
}
```

## API設計

### コンストラクタ — 常にSolid確定

```rust
impl Solid {
    fn box_from_corners(corner_1: DVec3, corner_2: DVec3) -> Solid;
    fn cylinder(p: DVec3, r: f64, dir: DVec3, h: f64) -> Solid;
    fn half_space(origin: DVec3, normal: DVec3) -> Solid;
}
```

### Boolean演算 — 常にCompoundで返す

```rust
impl Solid {
    fn union(&self, other: &Solid) -> Result<Compound, Error>;
    fn subtract(&self, other: &Solid) -> Result<Compound, Error>;
    fn intersect(&self, other: &Solid) -> Result<Compound, Error>;
}
```
あ
結果が単一Solidでも1要素のCompoundとして返す。

### Compound → Solid の変換

```rust
impl Compound {
    fn solids(&self) -> &[Solid];
    fn len(&self) -> usize;

    /// 要素が1個の時だけSome。
    fn into_solid(self) -> Option<Solid>;

    /// 要素が1個でなければErr。
    fn try_into_solid(self) -> Result<Solid, Error>;
}
```

### BooleanResult

```rust
pub struct BooleanResult {
    pub compound: Compound,
    pub new_faces: FaceGroup,
}
```

### I/O

```rust
// STEP読み込みは常にCompound（複数Solidを含みうる）
fn read_step(reader: impl Read) -> Result<Compound, Error>;

// 書き出しはCompound単位
fn write_step(compound: &Compound, writer: impl Write) -> Result<(), Error>;

// Solid単体の書き出しも許可（1要素Compoundとして扱う）
impl Solid {
    fn write_step(&self, writer: impl Write) -> Result<(), Error>;
}
```

### Utils

```rust
// Solid → Solid を保証（内部でtry_into_solidで検証）
fn stretch_vector(solid: &Solid, origin: DVec3, delta: DVec3) -> Result<Solid, Error>;
fn revolve_section(solid: &Solid, ...) -> Result<Solid, Error>;
```

途中のboolean結果がCompoundになった場合、`try_into_solid()`で検証し、
複数Solidに分裂していればErrを返す。

## FFI層の変更

追加が必要なC++関数:

```cpp
// ShapeType判定（既存のTopoDS_Shapeから型を取得）
uint32_t shape_type(const TopoDS_Shape& shape);
// → TopAbs_COMPOUND=0, COMPSOLID=1, SOLID=2, SHELL=3, FACE=4, ...

// CompoundからSolidを列挙
// TopExp_Explorerで TopAbs_SOLID を探索すれば実装可能
std::unique_ptr<TopExp_Explorer> explore_solids(const TopoDS_Shape& shape);
```

既存の`boolean_fuse/cut/common`の戻り値は`TopoDS_Shape`のままでよい。
Rust側で`shape_type()`を確認し、Solid/Compoundに振り分ける。

## colormapの扱い

colormapはFace単位のTShapeIdで管理している。

- `Solid`が自身のcolormapを持つ（現状のShapeと同じ）
- `Compound`は`Vec<Solid>`なので各Solidがcolormapを持つ
- boolean演算時の色追跡ロジック（`collect_relay_mapping`等）は変更不要

## 移行

- breaking change: 0.4.0
- 現在の`Shape`を使っている外部コード（api project等）は全面書き換え
- `Shape`型は廃止（type aliasも残さない）

## 未決事項

- [ ] `Compound`を`Vec<Solid>`で持つか、`UniquePtr<TopoDS_Shape>`のまま持つか
  - `Vec<Solid>`: Rust側で明確、ただしCompound再構築時にC++へ戻す手間
  - `UniquePtr`: C++側との往復が楽、ただしSolid列挙が毎回Explorer走査
- [ ] `FaceGroup`は`Vec<Face>`か`UniquePtr<TopoDS_Shape>`(Compound of Faces)か
- [ ] `Compound`同士のboolean演算を許可するか（全Solidペアで実行？）
- [ ] `Edge`/`Wire`等の型も分離するか（現状は優先度低）
