# Дизайн: линейный размер на виде чертежа (drawing_add_linear_dimension) через API7

**Дата:** 2026-05-27
**Статус:** реализовано и проверено (7 интеграционных + unit, всего 203); ревью Codex спека и реализации учтено

## Правки по ревью реализации Codex

- **#4 нулевое значение** (важно): после `Update`+`Valid` проверяем `|NominalValue| > 1e-6` — ловит
  несовместимость ориентации с точками (horizontal для точек на одной вертикали: `|X2-X1|=0`), которую
  `RequireDistinctPoints` не видит. При нуле — откат (`dim.Delete`) + ошибка. Тест добавлен.
- **#3 Parallel** (важно): добавлен интеграционный тест — параллельный (0,0)-(30,40) → 50 (3-4-5).
  **Работает БЕЗ явного `Angle`** (КОМПАС авто-вычисляет угол по точкам) — Parallel оставлен.
- **#7 rollback** (важно): тест нулевой проекции проходит через ветку `dim.Delete()` (откат) —
  проверяет, что число размеров вида не изменилось. (Update==false/Valid==false без fault injection.)
- **#8 ассоциативность вида** (минор): осознанный контракт «первый НЕсистемный вид» (задокументирован
  в описании инструмента и ошибке FindView).

## Находка реализации: размеры НЕ в IView.ObjectCount

`IView.ObjectCount` считает **геометрию** (`IDrawingContainer`: линии/дуги), а размеры живут в
`ISymbols2DContainer` отдельно → после простановки размера `ObjectCount` НЕ меняется (проверено: 4→4).
Поэтому «размер попал в целевой вид» проверяем счётом `ISymbols2DContainer.LineDimensions.Count`
(helper `GetViewLineDimensionCountAsync`), а не `ObjectCount`. (Гарантия непустоты у стандартных видов
по `ObjectCount` остаётся верной — там именно геометрия.)

## Правки по ревью Codex

- **Нулевая длина** (#1): `DrawingValidation.RequireDistinctPoints(x1,y1,x2,y2)` — расстояние между
  точками > 1e-7 (иначе вырожденный размер); + проверка `dim.Valid` после `Update()` (откат);
  + негативный тест.
- **Контракт вида** (#4): честно — «первый НЕСИСТЕМНЫЙ вид (Number != 0)» при `viewNumber<=0`, без
  претензии на ассоциативность и без QI к `IAssociationView` (в наших чертежах иных видов нет).
- **null-check + порядок** (#9): `LineDimensions.Add()` проверять на null; `AutoNominalValue=true`
  выставлять явно; `NominalValue` читать ТОЛЬКО после `Update()==true && Valid==true`.
- **ViewNumbers** (#7): поле `required` в `DrawingViewsResult`; инициализатор и текст MCP-ответа
  `drawing_create_standard_views` обновить (вернуть номера видов).
- **Тесты** (#6): ObjectCount ЦЕЛЕВОГО вида до/после; адресация конкретного `viewNumber`; негатив на
  совпадающие точки. **viewNumber не найден** (#2) → понятная ошибка. 3D-конвертер точек (#8) — будущее.

## Цель

Веха 2D-ЧЕРТЁЖ, инкремент 3 (размеры). После видов и основной надписи — **линейный размер** на
ассоциативном виде: между двумя точками (в локальной СК вида) с авто-измерением длины. Размеры —
самый ценный элемент оформления (несут размерную информацию). Начинаем с линейного
(горизонтальный/вертикальный/параллельный); диаметральные/радиальные/угловые — далее.

## Спайк: механизм подтверждён вживую (НЕ разучивать)

Спайк (`DrawingDimensionSpikeTests`, зелёный) подтвердил путь API7:

```
IView view = первый вид с Number != 0;             // системный вид — #0, пропускаем
ISymbols2DContainer symbols = (ISymbols2DContainer)view;   // COM-QI ОТ ВИДА (не от документа!)
ILineDimension dim = symbols.LineDimensions.Add();
dim.X1=x1; dim.Y1=y1; dim.X2=x2; dim.Y2=y2;        // выносные точки (ЛОКАЛЬНАЯ СК вида, мм)
dim.X3=x3; dim.Y3=y3;                               // положение размерной линии
dim.Orientation = ksLinDHorizontal;                // 0=Parallel / 1=Horizontal / 2=Vertical
bool ok = dim.Update();
double value = ((IDimensionText)dim).NominalValue; // авто-измеренное значение (мм)
```

**Проверено (вид спереди коробки 40×30×20):** размер (0,0)-(40,0) горизонтальный →
`Update()=True`, `Valid=True`, `NominalValue == 40` (авто `|X2-X1|`), `view.ObjectCount` вырос.

**Подтверждённые факты:**
- **`ISymbols2DContainer` берётся COM-QI ОТ КОНКРЕТНОГО `IView`** — размер попадает именно в этот
  вид (не нужен `Current=true`). (Аналогично 3D: `part`→`IModelContainer`.)
- **Координаты X1..Y3 — ЛОКАЛЬНАЯ СК вида** (= координаты модели на плоскости вида, при масштабе
  1:1 совпадают с мм модели). НЕ координаты листа.
- **`AutoNominalValue=true` по умолчанию** → `NominalValue` = расстояние между точками
  (горизонтальный: `|X2-X1|`, вертикальный: `|Y2-Y1|`, параллельный: реальная длина). Значение
  читается `((IDimensionText)dim).NominalValue` (QI).
- `Orientation` (`ksLineDimensionOrientationEnum`): `ksLinDParallel=0`, `ksLinDHorizontal=1`,
  `ksLinDVertical=2` (в `Kompas6Constants`).
- `Update()` обязателен; перестроение документа не нужно.

## Адресация вида (часть инкремента)

Чтобы указать, НА КАКОМ виде ставить размер, нужен его `Number`. Сейчас
`drawing_create_standard_views` возвращает только `{Created, Total}`. **Расширяем `DrawingViewsResult`
полем `ViewNumbers` (номера созданных видов)** — уже собираются как `newViews` в
`CreateStandardViewsCore` (тривиально: `newViews.Select(v => v.Number)`). Это закрывает замечание
Codex из ревью видов (#10: «нет ссылок на созданные виды») и делает размеры адресуемыми.

## MCP-инструмент

| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_add_linear_dimension` | `x1,y1,x2,y2` (выносные точки), `x3,y3` (положение размерной линии), `orientation="horizontal"`, `viewNumber=0` | Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ СК вида (мм). `orientation`: horizontal/vertical/parallel. `viewNumber`: номер вида (0 = первый ассоциативный/главный). Значение измеряется автоматически. Возвращает измеренное значение и номер вида. |

Контракт: активный документ — чертёж; на чертеже есть хотя бы один ассоциативный вид. Координаты —
локальные вида (см. выше). `drawing_create_standard_views` возвращает `ViewNumbers` для адресации.

## Архитектура

В существующем `DrawingService`. Свой enum `DimensionOrientation` (паттерн `MateType`/`StampField`).

- `src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs` — enum + static `DimensionOrientations`
  (`Parse(string)`, `ToKompas` → `ksLineDimensionOrientationEnum`).
- `DrawingService.AddLinearDimensionAsync(viewNumber, x1,y1,x2,y2,x3,y3, DimensionOrientation, ct)`.
- `DrawingViewsResult` += `IReadOnlyList<int> ViewNumbers`.
- Инструмент `drawing_add_linear_dimension` в `DrawingTools` (orientation строкой → Parse).

## Валидация (чистые static, unit-тест)

- `DimensionOrientations.Parse` — horizontal/vertical/parallel (+рус.) → enum; иначе `ArgumentException`.
- `DimensionOrientations.ToKompas` — enum → `ksLineDimensionOrientationEnum` (0/1/2).
- `DrawingValidation.RequireFiniteCoords(params)` — координаты конечны (NaN/Infinity → ошибка).

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

`AddLinearDimensionCore`: валидация координат + orientation → `RequireActiveDrawing` → найти вид
(`viewNumber<=0` → первый с `Number!=0`; иначе по `Number`, ошибка если нет) → `(ISymbols2DContainer)view`
→ `LineDimensions.Add()` → задать X1..Y3 + Orientation → `Update()` (FALSE → откат `dim.Delete()` +
ошибка) → проверить `dim.Valid` (false → откат + ошибка) → вернуть `AddedDimension { Value =
((IDimensionText)dim).NominalValue, ViewNumber = view.Number }`. RCW точечно не освобождаем (v2-2).

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

### Unit (`DimensionOrientationsTests`)
- `Parse` — horizontal/vertical/parallel и рус.-синонимы → enum; бросает на мусоре.
- `ToKompas` — каждый → 1/2/0.
- `RequireFiniteCoords` — бросает на NaN/Infinity; пропускает конечные.

### Integration (`DrawingDimensionTests`)
Паттерн: построить деталь → сохранить → создать чертёж → стандартные виды → размер → проверить →
`finally CloseAsync` (база `IntegrationTestBase`).
1. **Горизонтальный** (0,0)-(40,0) → `Value ≈ 40`; `view.ObjectCount` вырос.
2. **Вертикальный** (0,0)-(0,20) → `Value ≈ 20`.
3. **Адресация вида**: `CreateStandardViewsAsync` вернул `ViewNumbers` (3 шт); размер с конкретным
   `viewNumber` ставится в этот вид.
4. **Нет видов**: чертёж без видов → понятная ошибка.

(Сценарий 1 = промотированный спайк; спайк-файл удаляется.)

## Дальнейшее (вне спека)
- Диаметральные/радиальные/угловые размеры; привязка к геометрии вида (ассоциативно, `BaseObject`);
  допуски/обозначения; рамка/формат листа.
