# Дизайн: пакет B (шаг 1) — операция «Оболочка» (shell) **Дата:** 2026-05-26 **Статус:** на ревью (автономный режим; ревьюер — Codex) **Контекст:** kompas3d-mcp — MCP-сервер для КОМПАС-3D через COM API (.NET 8, C#). ## Цель Добавить формообразующую операцию **«Оболочка»** (придать стенкам толщину, удалив выбранные грани) — первый шаг пакета B «формообразующие». Самая востребованная операция этого пакета; опирается на уже готовый выбор грани по индексу из `list_faces`, без мульти-эскизной оркестрации (нужной для loft/sweep). Один MCP-инструмент `shell`. ## Не входит в объём (YAGNI) - Остальные операции пакета B (rib, draft, loft, sweep, hole) — отдельные шаги. - Переменная толщина по граням (`ksShellDefinition` задаёт одну общую `thickness`). - Закрытая оболочка без удаляемых граней — КОМПАС требует ≥1 удаляемую грань. ## Сигнатуры COM (подтверждены: рефлексия interop + SDK-база + пример Step3d1.cs) ``` Obj3dType.o3d_shellOperation = 43 ksShellDefinition: double thickness // толщина стенки, мм bool thinType // true = внутрь (габарит сохраняется), false = наружу object FaceArray() // ksEntityCollection удаляемых (открываемых) граней; нужна >=1 ``` **Паттерн** (из `Samples/CSharp.zip → Step3d1.cs`): ``` ksEntity ent = part.NewEntity(o3d_shellOperation) ksShellDefinition def = ent.GetDefinition() ksEntityCollection faces = def.FaceArray() faces.Add(<грань>) // одна или несколько удаляемых граней def.thickness = t; def.thinType = !outward ent.Create() ``` Порядок: сначала заполнить `FaceArray`, затем `thickness`/`thinType`, затем `Create()`. ## Новый MCP-инструмент (группа Feature) | Инструмент | Параметры | Поведение | |---|---|---| | `shell` | `faceIndices: int[]`, `thickness: double`, `outward: bool = false` | Превратить тело в оболочку толщиной `thickness` мм, удалив (открыв) грани с указанными индексами из `list_faces`. `outward=false` — толщина внутрь (габарит сохраняется), `true` — наружу. | **Маппинг:** `thinType = !outward`. **Выбор граней:** по стабильному индексу из `list_faces` (как `sketch_create_on_face_index`, `fillet_edge_index`). Индексы валидны, пока геометрия не менялась — берутся из свежего `list_faces`. Несколько граней → несколько `FaceArray.Add`. ## Реализация - **Метод модели** `ShellAsync(IReadOnlyList faceIndices, double thickness, bool outward, CancellationToken)` в `PartModeler.Features.cs` (формообразующая). Возвращает id операции (как extrude/revolve). - **Helper** `SelectFaceByIndex(ksPart, int)` в ядре `PartModeler.cs` — аналог существующего `SelectEdgeByIndex` (диапазон-проверка + `EntityCollection(o3d_face).GetByIndex`). - **Инструмент** `shell` в `FeatureTools.cs` (как `extrude_boss`/`revolve_boss`). - **Валидация** (inline, как `ExtrudeAsync`/`RevolveAsync`): `thickness > 0`; `faceIndices` не null и не пуст; **каждый индекс проверяется на диапазон до мутации модели** (`SelectFaceByIndex` бросает `ArgumentOutOfRangeException`, как `SelectEdgeByIndex`); **дубли индексов отбрасываются** (`Distinct`), чтобы не добавлять грань в `FaceArray` дважды; `FaceArray()` проверяется на null (`as ksEntityCollection ?? throw`). Возврат `Create()==FALSE` → `InvalidOperationException` с пояснением вероятных причин (толщина больше локального радиуса/толщины стенки; несовместимая топология). - **Известное ограничение** (документируем, не обрабатываем): на телах со скруглениями/фасками и на вогнутых/криволинейных гранях оболочка может не построиться (`Create()==FALSE`) даже при формально корректных входных данных — это ограничение ядра КОМПАС, причину API не сообщает. ## Тестирование **Integration** (`Category=Integration`, `KompasFixture`, в `SketchPrimitivesTests` или новом `FeatureOpsTests`): коробка 40×30×20 (`AddRectangle` → `Extrude`) → `list_faces` → найти верхнюю грань (нормаль +Z или по площади) → `ShellAsync([верхняя], thickness=2, outward=false)` → объём существенно уменьшается (тело стало полым с открытым верхом) и остаётся > 0. Точная проверка: оболочка 2 мм с открытым верхом = `24000 − 36×26×18 = 24000 − 16848 = 7152 мм³`. Утверждение: `after < before` и `after ∈ (7152·0.95, 7152·1.05)` (≈6794…7510 мм³). Если КОМПАС считает полость иначе — скорректировать ожидание по фактическому замеру, сохранив узкий допуск. Unit отдельный не вводим: маппинг `thinType=!outward` тривиален и инлайнится; валидация параметров — в стиле существующих операций (`extrude`/`revolve` тоже покрыты интеграционно). ## Влияние на документацию После реализации (через `docs-delegate`): счётчик инструментов +1 (→51), тестов +1; добавить `shell` в описание формообразующих в `CLAUDE.md`/`README.md`/`docs/ARCHITECTURE.md`/ `docs/presentation.html`; отметить начало пакета B.