# Дизайн: уклон граней (draft / incline) через API5 **Дата:** 2026-05-27 **Статус:** реализовано и проверено (тест зелёный); на ревью (ревьюер — Codex) ## Цель Добавить операцию **«Уклон»** (draft) — наклонить грани на угол относительно нейтральной (опорной) плоскости. Приоритет 1 текущей сессии. ## Ключевой факт: уклон ЕСТЬ в API5 (вопреки прежнему предположению) Память `kompas-formative-features-api5` ошибочно считала draft недоступным в API5 — она искала тип со словом **Draft**, но операция называется **«Уклон» = Incline**. Рефлексия подтвердила: `Obj3dType.o3d_incline=42` + **`ksInclineDefinition`** есть в `Kompas6API5.dll`. Это стандартный API5-паттерн (как shell/rib/fillet) — НЕ нужен API7. Плюс: операция регистрируется в реестре `_features`, значит массивы/зеркало пакета C к ней применимы. > `o3d_DraftFromEdges=644` (API7 `IDraftFromEdges`) — это ДРУГАЯ операция «уклон от базовой линии» > (v22), без нейтральной плоскости. Для обычного уклона граней используем `IIncline`/`ksInclineDefinition`. ## Сигнатуры (рефлексия interop + справка) ``` Obj3dType.o3d_incline = 42 ksInclineDefinition: double angle // угол уклона (градусы) bool direction // ЭМПИРИЧЕСКИ: false=расширение, true=сужение (обратно справке!) object FaceArray() // ksEntityCollection уклоняемых граней (Add по одной) bool SetPlane(object) // нейтральная (опорная) плоскость — ksEntity object GetPlane() ``` **Паттерн:** `NewEntity(o3d_incline)` → `GetDefinition()` → `FaceArray().Add(face)` (по граням) → `SetPlane(нейтральная)` → `angle` → `direction` → `Create()` → регистрация в `_features`. **Нейтральная плоскость** — сечение тела в ней остаётся неизменным; грани уклоняются вокруг линии пересечения с ней. На первом этапе — базовая координатная плоскость (XOY/XOZ/YOZ). **Направление (важно):** в interop `direction=false` даёт **расширение**, `true` — **сужение** (проверено объёмом; обратно тексту справки). Контракт инструмента: `outward` (true=расширение), маппинг `def.direction = !outward`. ## MCP-инструмент (группа Feature) | Инструмент | Параметры | Поведение | |---|---|---| | `draft` | `faceIndices: int[]`, `neutralPlane: "XOY"\|"XOZ"\|"YOZ"`, `angle: double`, `outward=false` | Уклон граней `faceIndices` (из list_faces) на `angle`° относительно нейтральной координатной плоскости. `outward` — расширение/сужение. Возвращает id. | **Решения (YAGNI):** нейтральная плоскость — только координатная (как mirror_operation); уклон от ребра (`IDraftFromEdges`) — отдельная задача позже. Грани — по стабильному индексу из list_faces. ## Реализация - **Метод** `DraftAsync(IReadOnlyList faceIndices, BasePlane neutralPlane, double angle, bool outward, ct)` в `PartModeler.Features.cs`. Валидация: `angle` конечен и в (0; 90); `faceIndices` не пуст, `Distinct`; диапазон всех индексов проверяется до мутации (`SelectFaceByIndex`). - **Инструмент** `draft` в `FeatureTools.cs` (`neutralPlane` парсится `BasePlanes.Parse`). - Транзитные RCW не освобождаем точечно — консистентно (долг v2-2). - `Create()==FALSE` → `InvalidOperationException` (грань не граничит с нейтральной плоскостью?). ## Тестирование (Integration, `FeatureOpsTests`) **Уклон**: коробка 40×40×20 (низ на Z=0) → 4 боковые грани (площадь 800) под уклон 10° внутрь (`outward=false`) относительно XOY → усечённая пирамида: верх ≈ 40−2·20·tan10°=32.95; `V = (20/3)(1600+1085.5+√(1600·1085.5)) ≈ 26689` мм³; `InRange(after, ±10%)` и `after < before`. ✓ Боковые грани выбираются из `ListFaces` по площади 800 (тип plane).