115fcb8759
ShellAsync (o3d_shellOperation): удаление граней по индексам + толщина стенки, thinType=!outward. SelectFaceByIndex в ядре, инструмент shell в FeatureTools. Валидация: thickness>0, дедупликация индексов, проверка диапазона до мутации. Integration-тест: коробка 40x30x20 → оболочка 2мм → 7152 мм³ (±5%). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
89 lines
6.7 KiB
Markdown
89 lines
6.7 KiB
Markdown
# Дизайн: пакет 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<int> 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.
|