# Дизайн: пакет B (шаг 3) — кинематическая операция (sweep)

**Дата:** 2026-05-26
**Статус:** на ревью (автономный режим; ревьюер — Codex)

## Цель

Добавить формообразующую **кинематическую операцию** (sweep) — переместить профиль вдоль
траектории, образуя тело. Третий шаг пакета B. Один MCP-инструмент `sweep`. В отличие от
loft (требует параллельные сечения → offset-плоскость, пакет E), sweep естественно работает с
базовыми плоскостями: профиль на одной, траектория на перпендикулярной.

## Сигнатуры COM (рефлексия interop)

```
Obj3dType.o3d_baseEvolution = 45
ksBaseEvolutionDefinition:
    short   sketchShiftType                 // тип переноса сечения вдоль пути
    bool    SetSketch(object)               // профиль (сечение)
    object  PathPartArray()                 // ksEntityCollection сегментов траектории
    bool    SetThinParam(bool thin, short thinType, double normal, double reverse)
```

**Паттерн:** `NewEntity(o3d_baseEvolution)` → `GetDefinition()` → `SetSketch(профиль)` →
`PathPartArray().Add(траектория)` → `sketchShiftType` → `SetThinParam(false,…)` (сплошное) →
`Create()`.

## Новый MCP-инструмент (группа Feature)

| Инструмент | Параметры | Поведение |
|---|---|---|
| `sweep` | `profileSketchId: int`, `pathSketchId: int` | Кинематическая операция: переместить замкнутый профиль `profileSketchId` вдоль траектории `pathSketchId`, образуя сплошное тело. Профиль и траектория — на разных (обычно перпендикулярных) плоскостях; начало траектории совпадает с плоскостью профиля. Возвращает id операции. |

**Решения (YAGNI):** тонкостенный режим (`thin`) и `sketchShiftType` наружу не выставляем —
сплошное тело, `sketchShiftType=0` (перенос с сохранением угла). Можно расширить позже.

## Реализация

- **Метод** `SweepAsync(int profileSketchId, int pathSketchId, CancellationToken)` в
  `PartModeler.Features.cs`. Оба эскиза закрываются перед операцией (`CloseSketchCore`);
  берутся `sketch.Entity` из реестра. `profileSketchId == pathSketchId` → `ArgumentException`.
  Возвращает id операции.
- **Инструмент** `sweep` в `FeatureTools.cs`.
- `PathPartArray()` проверяется на null (`as ksEntityCollection ?? throw`).
- Транзитный `ksBaseEvolutionDefinition` не освобождается — консистентно (долг v2-2).
- `Create()==FALSE` → `InvalidOperationException` (профиль/траектория несовместимы: начало пути
  не на плоскости профиля, самопересечение?).

## Тестирование

**Integration** (`FeatureOpsTests`): профиль — окружность R5 на XOY (центр 0,0); траектория —
вертикальный отрезок на XOZ `(0,0)→(0,50)` (начинается в центре профиля, идёт по +Z) →
`SweepAsync(profile, path)` → цилиндр R5×H50, объём `π·25·50 ≈ 3927 мм³`. Проверка
`InRange(volume, 3927·0.95, 3927·1.05)`. Если `sketchShiftType=0` не строит — перебрать тип
(0/1) по факту, сохранив проверку объёма.
