diff --git a/docs/superpowers/specs/2026-05-27-drawing-standard-views-design.md b/docs/superpowers/specs/2026-05-27-drawing-standard-views-design.md new file mode 100644 index 0000000..8f29851 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-drawing-standard-views-design.md @@ -0,0 +1,101 @@ +# Дизайн: стандартные виды чертежа (drawing_create_standard_views) через API7 + +**Дата:** 2026-05-27 +**Статус:** спайк зелёный (механизм подтверждён вживую); спроектировано; на ревью (Codex) + +## Цель + +Веха 2 — **2D-ЧЕРТЁЖ** (новый класс функционала после деталей и сборок). Инкремент 1: создать на +активном чертеже **стандартные ассоциативные виды** (спереди/сверху/слева) по сохранённой 3D-модели +`.m3d`/`.a3d`. Это фундамент чертежа — без видов лист пуст. Чертёж проверяется не объёмом, а +**числом созданных видов**. + +## Спайк: механизм подтверждён вживую (НЕ разучивать) + +Спайк (`DrawingSpikeTests`, зелёный) подтвердил путь API7: + +``` +IKompasDocument2D doc2d = (IKompasDocument2D)app.ActiveDocument; // активный чертёж +IViews views = doc2d.ViewsAndLayersManager.Views; // COM-свойства (не QI) +object[] projTypes = { 1, 3, 5 }; // ksPtFront=1, ksPtUp=3, ksPtLeft=5 (SAFEARRAY VT_I4) +bool ok = views.AddStandartViews(path, "#Спереди", projTypes, X:100, Y:150, Scale:1.0, DX:20, DY:20); +int count = views.Count; +``` + +**Проверенные факты:** +- `object[] { 1, 3, 5 }` **маршалится в SAFEARRAY VT_I4 без проблем** (риск из ревью SDK снят). +- Новый чертёж стартует с `Views.Count == 1` (системный вид #0). После `AddStandartViews` с тремя + кодами проекций `Count == 4` → **создано ровно 3 вида** (спереди/сверху/слева). `ok == True`. +- `ProjectionName = "#Спереди"` — ориентация главного вида (стандартное имя проекции 3D-модели). +- `IKompasDocument2D` напрямую (без QI к `IKompasDocument2D1`) даёт `ViewsAndLayersManager.Views` + (подтверждено рефлексией interop — отличие от ревью SDK). +- Явный rebuild/save после `AddStandartViews` НЕ нужен — виды уже в документе (`Count` сразу 4). + +**Параметры `AddStandartViews(FileName, ProjectionName, ProjectionsTypes, X, Y, Scale, DX, DY)`:** +- `FileName` — путь к `.m3d`/`.a3d`. +- `ProjectionName` — ориентация главного вида (`"#Спереди"`). +- `ProjectionsTypes` — `SAFEARRAY VT_I4` кодов `ksRelativeProjectionTypeEnum`: `ksPtFront=1`, + `ksPtRear=2`, `ksPtUp=3`, `ksPtDown=4`, `ksPtLeft=5`, `ksPtRight=6`, `ksPtIsoXYZ=7`. +- `X, Y` — точка привязки главного вида на листе, мм. +- `Scale` — масштаб (1.0 = 1:1). +- `DX, DY` — зазоры между видами, мм. Возврат: `bool` (успех). + +## Архитектура + +Новый класс (как сборки — отдельный от `PartModeler`). Сервис `DrawingService` (API7), инструменты +`DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`. + +- `src/Kompas.Mcp.Core/Drawings/DrawingService.cs` — сервис. +- `src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs` — чистые static-валидаторы (unit). +- `src/Kompas.Mcp.Host/Tools/DrawingTools.cs` — MCP-инструмент. +- DI: `AddSingleton()` в `Program.cs`. + +## MCP-инструмент + +| Инструмент | Параметры | Поведение | +|---|---|---| +| `drawing_create_standard_views` | `partFilePath: string`, `scale=1.0` | На активном чертеже создать стандартные ассоциативные виды (спереди/сверху/слева) сохранённой детали/сборки. Возвращает число созданных видов. | + +Контракт: активный документ — чертёж (`ksDocumentDrawing`); файл модели существует. Набор видов +фиксирован (спереди+сверху+слева, 3 вида); позиция/зазоры — дефолты (X=100, Y=150, DX=20, DY=20 мм). +Выбор набора видов / позиции / изометрии — будущие инкременты (YAGNI). + +## Валидация (чистые static, unit-тест) + +`DrawingValidation`: +- `NormalizeModelPath(string? path)` — `Trim` → непустота → расширение `.m3d`/`.a3d` → `Path.GetFullPath`. +- `RequireScale(double scale)` — конечный и `> 0`. + +`File.Exists` — в сервисе (`FileNotFoundException` с абсолютным путём). + +## Проверка активного документа + +`RequireActiveDrawing()` — раздельные ошибки: нет документа / тип != `ksDocumentDrawing` / не +приводится к `IKompasDocument2D`. + +## Реализация + +`CreateStandardViewsAsync(partFilePath, scale, ct)` — на STA-потоке: валидация (`NormalizeModelPath`, +`RequireScale`) → `File.Exists` → `RequireActiveDrawing` → `views.Count` (before) → +`AddStandartViews(path, "#Спереди", {1,3,5}, 100, 150, scale, 20, 20)` (результат проверяем: FALSE → +ошибка) → `created = views.Count - before` (ожидаем 3; если 0 — ошибка «виды не созданы»). Возврат — +число созданных видов. Транзитные RCW не освобождаем точечно (долг v2-2). + +## Тестирование + +### Unit (`DrawingValidationTests`) +- `NormalizeModelPath` — бросает на пустом/чужом расширении (`.step`); принимает `.m3d`/`.a3d` + (регистр, пробелы), возвращает абсолютный путь. +- `RequireScale` — бросает на 0/отрицательном/NaN/Infinity; принимает положительный. + +### Integration (`DrawingTests`) +Паттерн: построить деталь 40×30×20 → `SaveAsAsync(.m3d)` → проверить файл → `CloseAsync` → создать +чертёж → `CreateStandardViewsAsync` → проверить → `finally CloseAsync` (база `IntegrationTestBase`). +1. **Создание видов**: `AddStandartViews` → возврат `== 3`; `Views.Count` вырос на 3 (с 1 до 4). +2. **Не-чертёж**: активный документ — деталь → `CreateStandardViewsAsync` бросает (понятная ошибка). + +(Сценарий 1 = промотированный спайк; спайк-файл удаляется.) + +## Дальнейшее (вне спека) +- Выбор набора видов (+изометрия, +сечения), позиция/масштаб листа, размеры/обозначения на видах, + рамка/основная надпись.