7359337a95
README/CLAUDE/ARCHITECTURE/OPEN_QUESTIONS/presentation.html: счётчики 75→76, 203→218; drawing_add_diametral_dimension в группе Drawing; факты (ISymbols2DContainer. DiametralDimensions, Xc/Yc/Radius/Angle радианы, NominalValue=2R, RequireSymbols2DContainer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
203 lines
30 KiB
Markdown
203 lines
30 KiB
Markdown
# Открытые вопросы для проработки
|
||
|
||
Журнал спорных моментов и решений, принятых автономно (ночная сессия 2026-05-25/26).
|
||
Помечено: ⚠️ = требует вашего решения; ✅ = решено по умолчанию, можно пересмотреть.
|
||
|
||
---
|
||
|
||
## ⚠️ 1. API5 vs API7 для построения 3D
|
||
**Контекст.** Примеры SDK строят 3D через **API5** (`ksPart.NewEntity(o3d_*)` →
|
||
`ksBossExtrusionDefinition` → `Create()`). API7 предлагает `IPart7` → `IModelContainer` →
|
||
`IExtrusions.Add()`.
|
||
**Решение по умолчанию (✅ в коде):** в v1 строю 3D через **API5 `ksPart`** (надёжно, повторяет
|
||
рабочие примеры Step3d1). API7 использую для приложения/документов/версии.
|
||
**На проработку:** переводить ли операции на чистый API7 позже (плюс — единообразие, минус — риск).
|
||
|
||
## ✅ 2. Точка входа подключения — API5
|
||
Для построения 3D нужен `KompasObject` (API5), а для снимка — `ksDocument3D` (API5).
|
||
Поэтому `KompasSession` подключается через `KOMPAS.Application.5` → `KompasObject`, затем
|
||
`ksGetApplication7()` → `IApplication`. Держит обе ссылки. (Ранее в спайке заходили через `.7`.)
|
||
|
||
## ✅ 3. Состав проектов v1
|
||
Вместо 4 проектов (Host/Tools/Core/Interop) сделано компактно: **Core** (COM + сервисы) +
|
||
**Host** (MCP + определения инструментов) + **Tests**. Interop — вендорские DLL в `libs/`.
|
||
Разнести на больше проектов можно позже.
|
||
|
||
## ✅ 4. Тестовые артефакты
|
||
Тестовые документы КОМПАС сохраняю в `.scratch/` (gitignore). Снимки рендера — туда же,
|
||
для визуальной проверки.
|
||
|
||
---
|
||
|
||
## Само-ревью кода (ночная сессия) — итоги
|
||
|
||
**Исправлено сразу:**
|
||
- C1/C2 — утечки COM и устаревшие id: `PartModeler` освобождает RCW и сбрасывает реестр
|
||
(`ResetAsync`/`Dispose`), `DocumentTools` сбрасывает реестр при create/open/close;
|
||
`DocumentService` освобождает транзитные `IDocuments`/`IKompasDocument`.
|
||
- M3 — `KompasSession.Dispose`: ограниченное ожидание (5 c) вместо вечного блока.
|
||
- M4 — `ExtrudeAsync`: убран `dynamic`, конкретные `ksBossExtrusionDefinition`/
|
||
`ksCutExtrusionDefinition`; `throughAll` разрешён только для выреза.
|
||
- m7 — `extrude_cut`: дефолт `throughAll=false`, без молчаливой подмены `depth`.
|
||
- Регрессионный тест: после `ResetAsync` старый id эскиза недействителен.
|
||
|
||
**⚠️ Отложено на проработку (из ревью):**
|
||
- M5 — отмена/насос сообщений: `CancellationToken` не прерывает уже идущий COM-вызов; зависший
|
||
вызов (модальный диалог КОМПАС) блокирует всю очередь. Возможное решение — диспетчер с насосом
|
||
сообщений + подавление диалогов. Связано с [конкурентностью MCP].
|
||
- m6 — `colorBPP=24` для снимка: для GIF (палитра) может не подойти; снимок по умолчанию PNG.
|
||
- m8 — приведения enum к `short` для `GetPart/NewEntity/GetDefaultEntity`: на v24 работает
|
||
(тесты зелёные), но стоит сверить точные типы параметров в interop.
|
||
- m10 — `_app/_kompas` читаются из `IsConnected/Status()` вне STA-потока без `volatile`
|
||
(доброкачественное устаревшее чтение).
|
||
- m11 — трансляция `COMException`/HRESULT в понятные сообщения (сейчас понятны только наши
|
||
`InvalidOperationException`); добавить текст последней ошибки КОМПАС.
|
||
|
||
### Ревью v2 (грань/вращение/скругление/фаска/габариты/список граней)
|
||
**⚠️ Отложено:**
|
||
- v2-1 — **направление операций на грани**: для бобышки/выреза на выбранной грани параметр
|
||
`forward` зависит от ориентации нормали грани, которая для эскиза-на-грани оказывается
|
||
направленной *внутрь* тела (boss с `forward=true` уходит в материал — прироста нет;
|
||
наружу даёт `forward=false`). Сейчас выбор направления — на стороне агента (по снимку).
|
||
На проработку: автоопределение «наружу» по нормали грани, либо опция `auto`.
|
||
- v2-2 — **утечки транзитных RCW в `PartModeler`**: `GetTopPart()` и коллекции
|
||
`EntityCollection(o3d_face/o3d_edge)` не освобождаются (унаследованный паттерн; `QueryService`
|
||
их освобождает, `PartModeler` — только зарегистрированные сущности в `ResetCore`). Накапливается
|
||
за длинную сессию. На проработку: единый `using`/release транзитов в построителе.
|
||
Тот же паттерн у новых членов (`SelectFaceByIndex`, `ShellAsync`: транзитные `ksShellDefinition`/
|
||
`FaceArray()`/коллекции граней не освобождаются — сознательно, ради консистентности с
|
||
`Fillet`/`Chamfer`; войдёт в общий рефакторинг release транзитов).
|
||
- v2-3 — **стабильность индексов граней** (`list_faces` → `sketch_create_on_face_index`):
|
||
индекс валиден, пока геометрия не меняется между вызовами. После любой операции порядок/состав
|
||
граней может измениться — переиспользовать старый индекс нельзя, нужно перезапросить `list_faces`.
|
||
- v2-4 — `fillet/chamfer` строятся по одному ребру (первое из `SelectByPoint`/коллекции);
|
||
`tangent=false`. На проработку: набор рёбер, скругление по касательным, переменный радиус.
|
||
|
||
**✅ Решено по ходу:**
|
||
- Угол вращения `revolve_*` — в градусах, `directionType=dtNormal`, `toroidShapeType=сфероид`,
|
||
`SetThinParam(false,…)` для сплошного тела. Ось вращения — осевая линия (системный стиль 3).
|
||
- Габариты — `ksPart.GetGabarit(full=false)` (только тела), координаты в мм.
|
||
|
||
## Журнал решений по ходу
|
||
(дополняется автоматически во время работы)
|
||
|
||
### ⚠️ Конкурентность вызовов MCP и состояние сессии
|
||
MCP-сервер обрабатывает запросы **конкурентно**. Цикл моделирования с реестром эскизов по id
|
||
(`sketch_create` → `sketch_add_*`) предполагает, что клиент **ждёт ответа** на каждый вызов перед
|
||
следующим (нормальное поведение LLM-клиентов). При «конвейерной» отправке зависимых вызовов
|
||
возможна гонка (id ещё не зарегистрирован). STA-диспетчер сериализует COM, но не порядок постановки задач.
|
||
**На проработку:** нужна ли серверная сериализация вызовов инструментов (очередь) для надёжности
|
||
stateful-сессии? Пока полагаемся на последовательное поведение клиента.
|
||
|
||
### ✅ Возврат изображения: ImageContentBlock.FromBytes
|
||
Поле `ImageContentBlock.Data` хранит **base64-байты**, а не сырые. Сырые байты PNG нужно
|
||
передавать через `ImageContentBlock.FromBytes(bytes, mimeType)` — он сам кодирует. Прямое
|
||
присваивание `Data = rawBytes` портит изображение на проводе. Исправлено в VisionTools.
|
||
|
||
### ✅ МЦХ (get_part_info): API5 `CalcMassInertiaProperties`, а не API7
|
||
Сначала пробовали API7 `IMassInertiaParam7` (от `TopPart`). Две проблемы:
|
||
1. **Единицы** — отдаёт в единицах отображения МЦХ документа (по умолчанию см³/см²/г), зависит от настроек.
|
||
2. **Залипающий кэш** — после первого расчёта свойство `Actual` остаётся `TRUE` и не сбрасывается
|
||
при изменении геометрии через API5; `Calculate()` возвращает `FALSE` и не пересчитывает.
|
||
Сценарий «запрос → достройка → запрос» отдавал **устаревшее** значение. Сброс через
|
||
`RebuildModel`, переключение `MassSettingMode` — не помогли.
|
||
|
||
**Решение (✅ в коде):** считаем через API5 **`ksPart.CalcMassInertiaProperties(bitVector)`** →
|
||
`ksMassInertiaParam` (`v`/`m`/`F`/`xc`/`yc`/`zc`). Считает «по запросу» (без залипания), единицы
|
||
задаются битовым вектором: `ST_MIX_MM (0x1) | ST_MIX_KG (0x10)` → **мм³, мм², кг, мм**.
|
||
Согласовано с тем, что деталь строится через API5 `ksPart`. Проверено двумя тестами:
|
||
цилиндр R10×H20 (V≈6283 мм³, S≈1885 мм², m≈0.0494 кг, Zc=10 мм) и «запрос → штифт на грани → запрос»
|
||
(виден корректный прирост ≈785 мм³). Материал по умолчанию — сталь (ρ≈7.856 г/см³).
|
||
|
||
### ✅ Снимок 3D: рендер через файл, а не resultArrayBytes
|
||
`ksDocument3D.SaveAsToRasterFormat(fileName, param)` при **непустом** `fileName` возвращает TRUE,
|
||
пишет корректный PNG на диск, но `param.resultArrayBytes` остаётся **пустым**. Поэтому
|
||
`SnapshotService` рендерит во временный файл и читает байты обратно (надёжно проверено на v24 Home).
|
||
**На проработку:** пустое имя файла + `returnResultAsArrayBytes=true` для чистого in-memory —
|
||
не проверено; текущий путь через temp-файл работает.
|
||
|
||
### ✅ STEP импорт/экспорт и list_components — реализованы
|
||
`import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (`QueryService.ListComponentsAsync`) добавлены в v2+.
|
||
Проверенные COM-приёмы:
|
||
- Импорт: `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (код формата, не путь к DLL) → `IConverter.ConverterParameters(cmd)` → `IAdditionConvertParameters.Format=ksConverterFromSTEP` → `IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`.
|
||
- Экспорт: `ConvertToAdditionFormat` с форматами AP203/AP214/AP242 (`StepFormat` enum в `Core/Conversion/StepFormat.cs`).
|
||
- Param-коклассы (AdditionConvertParameters) — **не** CoCreatable, только через фабрики.
|
||
- Обход сборки: `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`.
|
||
- Извлечение детали: `prm.NeedCreateComponentsFiles=true` + `IPart7.OpenSourceDocument`.
|
||
|
||
### ✅ Прямое редактирование импортированной B-rep — move_face реализован
|
||
**Решено.** Перемещение грани продуктизировано как инструмент `move_face` / сервис `FaceEditService` (`src/Kompas.Mcp.Core/Editing/FaceEditService.cs`). Зарегистрирован в DI (`Program.cs`); интеграционный тест — `Integration/FaceEditTests.cs`.
|
||
|
||
Проверенный паттерн:
|
||
- Объект грани берётся через **`IPart7.FindObjectsByPoint(x, y, z, true)`** → результат приводится к `KompasAPI7.IFace`. Не требует `EntityCollection`/`SelectByPoint` через API5.
|
||
- Контейнеры операций не создаются отдельно: `(ISurfaceContainer)part` и `(IModelContainer)part` — COM-QI от `IPart7` в рантайме.
|
||
- `FaceMover`: `SetFaces(faces)` + `Offset(distance)` + `Direction(true/false = наружу/внутрь)` + `Update()`.
|
||
- Работает как на параметрической геометрии, так и на импортированной B-rep (проверено: деталь top_spacer, высота 39.45 → 41.45 мм).
|
||
|
||
Полный «рассечь→раздвинуть→объединить» (`SplitSolids`/`BodyRepositions`) — механика есть и драйвится, но отдельными MCP-инструментами не оформлена (возможная будущая опция).
|
||
|
||
### ✅ Структурный осмотр модели — инспекция реализована
|
||
**Решено.** 7 инструментов (`InspectionTools.cs`, `ModelInspectionService.cs`, `InspectionText.cs`):
|
||
- `describe_model` — структурный «паспорт» детали: габарит, МЦХ, тела, сводка топологии, дерево операций с параметрами, переменные. **Предпочтителен перед `model_snapshot`**.
|
||
- `list_features` / `list_bodies` / `list_variables` — детальные срезы.
|
||
- `describe_face` / `describe_edge` — drill-down по индексу.
|
||
- `measure` — расстояние/угол между объектами (`ksMeasurer`).
|
||
|
||
Ключевые паттерны: дерево — `ksPart.GetFeature()` → `SubFeatureCollection(true,false)`; тела — `BodyCollection()` → `ksBody`; переменные — `VariableCollection()` → `ksVariable`. Детект «импорт без истории»: `bodyCount>0 && нет формообразующих операций && features.Count<=bodyCount+1`. Итог: **62 инструмента, 101 тест.**
|
||
|
||
### ✅ hole — реализован через API7
|
||
**Решено.** Инструмент `hole` добавлен через API7 — в API5 интерфейс `ksHoleDefinition` отсутствует в interop (`o3d_holeOperation=52` есть, но определения нет). Новый сервис `HoleService` (`src/Kompas.Mcp.Core/Modeling/HoleService.cs`) не является методом `PartModeler`.
|
||
|
||
Паттерн размещения: `(IModelContainer)part7` → `Points3D.Add()` (создаёт `IPoint3D` как центр) → `Holes3D.Add()` → `IHole3D` → `(IHoleDisposal)hole` (`BaseSurface` = грань по `FindObjectsByPoint`, `Perpendicular=true`, `AssociationVertex=point`) → `Update()` → `RebuildDocument()`. Эскиз размещения не нужен.
|
||
|
||
**Ключевая тонкость — направление по объёму:** `Update()` возвращает TRUE даже если сверлит «в воздух» снаружи тела (убыли материала нет). Решение: попытка `Direction=true` → Rebuild → сравниваем объём через API5 `CalcMassInertiaProperties`; если не убыл — пробуем `Direction=false`. Тот же класс проблем, что у boss/cut на грани (непредсказуемая ориентация нормали). При неуспехе обеих попыток — откат через `IFeature7.Delete()`. Добавлена валидация `double.IsFinite` (Infinity проходил мимо `>0`).
|
||
|
||
Отверстие не попадает в реестр `_features` API5 → возвращает подтверждение без id. +3 интеграционных теста (`HoleTests.cs`).
|
||
|
||
### ✅ draft (уклон) — реализован через API5 Incline, а не API7
|
||
**Решено.** Ранее считалось, что уклон недоступен в API5 и требует API7. Это было заблуждением: поиск вёлся по слову "Draft", тогда как операция в API5 называется **Incline** (`ksInclineDefinition`, `o3d_incline=42`). Реализация — стандартный API5-паттерн `PartModeler` (как shell/rib/loft): `NewEntity(o3d_incline)` → `ksInclineDefinition` → `FaceArray()` (грани по индексам) → `SetPlane(координатная плоскость)` → `angle` → `direction` → `Create()` → регистрация в `_features`.
|
||
|
||
**Ключевая тонкость (эмпирика):** `direction=false`=расширение (outward), `direction=true`=сужение — **обратно справке**. Маппинг в коде: `def.direction = !outward`. Тот же класс проблем с направлением, что у boss/cut (v2-1). Валидация угла вынесена в `SketchGeometry.RequireDraftAngle(0<angle<90)`. +9 unit-тестов + 2 integration-теста. Итог: **64 инструмента, 115 тестов (69 unit + 46 integration).**
|
||
|
||
Примечание: `o3d_DraftFromEdges=644` / `IDraftFromEdges` — это другая операция «уклон от базовой линии» (API7), здесь не используется.
|
||
|
||
### ✅ Пакет D «параметрика» — CRUD переменных реализован
|
||
**Решено (start).** 3 новых инструмента: `create_variable`, `set_variable`, `delete_variable` — `VariableService` (`src/Kompas.Mcp.Core/Modeling/VariableService.cs`) + `VariableTools` (`src/Kompas.Mcp.Host/Tools/VariableTools.cs`). Исправлен `list_variables` в `ModelInspectionService.ReadVariables`. Итог (на тот момент): **67 инструментов, 116 тестов (69 unit + 47 integration).**
|
||
|
||
**Ключевые API-тонкости (эмпирика):**
|
||
- Создание переменной — ТОЛЬКО через `ksPart.GetFeature().VariableCollection` (свойство-коллекция на корневом `ksFeature`). `ksPart.VariableCollection()` (метод) возвращает только **внешние** переменные и непригоден для создания пользовательских.
|
||
- Expression — ведущее поле; Value — вычисленный результат. Изменяют значение через Expression.
|
||
- После `ksPart.RebuildModel()` RCW `ksVariable` застревает на старом значении → перечитывать из свежей коллекции (`ReadValueFresh`). Зависимые переменные пересчитываются автоматически.
|
||
- `RemoveVariable` возвращает FALSE при наличии зависимых переменных → удаление невозможно.
|
||
- `list_variables` исправлен: ранее читал `ksPart.VariableCollection()` (только внешние), теперь `ksPart.GetFeature().VariableCollection` (все), с фолбэком на старый путь.
|
||
|
||
**⚠️ Осознанное ограничение — геометрия не меняется:** переменная управляет геометрией только в параметрической модели, где размеры эскизов привязаны к именам переменных. Наши эскизы строятся литеральными координатами — `set_variable` хранит и вычисляет значение, но геометрию НЕ меняет.
|
||
Связь размеров эскизов с переменными (параметрические эскизы через API2D) — отдельный пласт, **не реализован**. Это осознанно задокументировано; реализация — продолжение пакета D.
|
||
|
||
### ✅ Параметрические эскизы (пакет D продолжение) — исследовано, недоступно через COM-API
|
||
**Исследовано и закрыто.** `ksCDimWithVariable` (размер с переменной) не конструируется из внешней автоматизации — интерфейс недоступен в COM-клиентском режиме. Связать размеры эскиза с именем переменной через API2D невозможно без исполнения кода внутри процесса КОМПАС (plugin). Подробности — `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`.
|
||
|
||
### ✅ Отверстие с цековкой и зенковкой — реализовано
|
||
**Решено.** 2 новых инструмента: `hole_counterbore` (`ksHTCounterbore`, `ISpotfacingHoleParameters`) и `hole_countersink` (`ksHTCountersinking`, `ICountersinkHoleParameters`). `HoleCore` рефакторен под `configure`-колбэк. Итог: **69 инструментов, 118 тестов (69 unit + 49 integration).**
|
||
|
||
### ✅ Коническое отверстие — реализовано
|
||
**Решено.** Новый инструмент `hole_conic` — коническое (конусное) отверстие, сужающееся вглубь. Параметры: `x,y,z`, `diameter` (диаметр у основания), `conicAngle` (угол конуса, 0<angle<180), `depth=0`, `throughAll=false`. Реализован через тот же `HoleCore`-паттерн (`configure`-колбэк): `HoleType=ksHTConic` + configure устанавливает `(IConicHoleParameters)hole.HoleParameters` (`ConicType=ksCNAngle`, `ConicAngle`). Завершает набор типов отверстий: простое (`hole`), цековка (`hole_counterbore`), зенковка (`hole_countersink`), коническое (`hole_conic`). Итог: **70 инструментов, 119 тестов (69 unit + 50 integration).**
|
||
|
||
### ✅ Вставка компонента в сборку — реализована (начало класса «Сборки»)
|
||
**Решено.** Новый инструмент `assembly_add_component(filePath, x, y, z)` — первый инструмент, который *пишет* в сборку (ранее `list_components` только читал). Сервис `AssemblyService` (`src/Kompas.Mcp.Core/Assemblies/`), инструменты — `AssemblyTools`. Паттерн API7: `IParts7.AddFromFile` → `IPlacement3D.SetOrigin` → `UpdatePlacement` → `RebuildModel(true)`. Ключевые тонкости: `UpdatePlacement` возвращает FALSE для вручную позиционируемого компонента — не ошибка; зафиксированный первый компонент двигается через `SetOrigin+RebuildModel` без разфиксации; `RequireActiveAssembly` проверяет тип документа до `TopPart`. Откат при неуспехе через `IFeature7.Delete`.
|
||
|
||
### ✅ Сопряжения в сборках — реализованы (coincidence/distance)
|
||
**Решено (инкремент 2).** Новый инструмент `assembly_add_mate(mateType, x1,y1,z1, x2,y2,z2, value=0)`. Паттерн API7: `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)` → `BaseObject1/2` (грани через `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` → `Update()` → `RebuildModel(true)`; проверка `mate.Valid`. Типы `mc_Coincidence=0`/`mc_Distance=5` проверены вживую. Enum `MateType` + static `Mates` в `src/Kompas.Mcp.Core/Assemblies/MateType.cs`. Итог: **72 инструмента, 156 тестов (98 unit + 58 integration).** Следующий шаг: прочие типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`).
|
||
|
||
### ✅ Стандартные виды чертежа — реализованы (начало вехи «2D-чертёж»)
|
||
**Решено.** Новый инструмент `drawing_create_standard_views(partFilePath, scale, x, y)` — создаёт ассоциативные виды (спереди/сверху/слева) на активном чертеже. Реализовано через API7 `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`), `DrawingTools`. Паттерн: `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, 20, 20)` — `object[]` как SAFEARRAY VT_I4. Проверено вживую: `Views.Count` 1→4, `ObjectCount>0` сразу (rebuild не нужен).
|
||
|
||
### ✅ Основная надпись (штамп) чертежа — реализована (инкремент 2 вехи «2D-чертёж»)
|
||
**Решено.** Новый инструмент `drawing_fill_title_block(designation?, name?, material?)` — заполняет графы основной надписи активного чертежа. Паттерн API7: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` → `stamp.Update()`. `IText.Str` замещает содержимое напрямую (повторная запись перезаписывает — `Clear` не нужен, подтверждено round-trip тестом). Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`; инструмент в `DrawingTools.cs`. Итог: **74 инструмента, 188 тестов (123 unit + 65 integration).** Следующий шаг: размеры на видах.
|
||
|
||
### ✅ Линейные размеры на видах чертежа — реализованы (инкремент 3 вехи «2D-чертёж»)
|
||
**Решено.** Новый инструмент `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — ставит линейный размер на виде активного чертежа. Паттерн API7: получить вид по `viewNumber` → COM-QI `(ISymbols2DContainer)view` (размер адресуется конкретному виду, а не листу) → `LineDimensions.Add()` → `ILineDimension` → `X1,Y1,X2,Y2` (выносные точки в **локальной СК вида**, мм), `X3,Y3` (положение размерной линии), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true` → `Update()` → `Valid` → значение `((IDimensionText)dim).NominalValue`. Откат `dim.Delete()` при ошибке или нулевом значении. **Ключевая находка: размеры НЕ входят в `IView.ObjectCount`** (тот считает геометрию `IDrawingContainer`); размеры живут в `ISymbols2DContainer` отдельно — попадание размера в вид проверяется через `ISymbols2DContainer.LineDimensions.Count`. `DrawingViewsResult` расширен полем `ViewNumbers` (номера созданных видов — для адресации при простановке размеров). Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. Итог: **75 инструментов, 203 теста (131 unit + 72 integration).** Следующий шаг: диаметральные/радиальные/угловые размеры, рамка/формат.
|
||
|
||
### ✅ Диаметральный размер на виде чертежа — реализован (инкремент 4 вехи «2D-чертёж»)
|
||
**Решено.** Новый инструмент `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber)` — ставит диаметральный размер (Ø) на виде активного чертежа. Паттерн API7: общий хелпер `RequireSymbols2DContainer(viewNumber)` (переиспользуется с линейным путём) → `DiametralDimensions.Add()` → `IDiametralDimension` (`Xc`, `Yc`, `Radius` в локальной СК вида, `Angle` в **радианах** через `DimensionAngles.ToRadians`, `AutoNominalValue=true`) → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue` = диаметр (2·Radius). Размер «свободный» (без `BaseObject` — ассоциативная привязка отложена). Валидатор `DrawingValidation.RequirePositiveRadius`. `DimensionAngles.ToRadians` (с проверкой переполнения) добавлен в `DimensionOrientation.cs`; `RequirePositiveRadius` — в `DrawingValidation.cs`; инструмент в `DrawingTools.cs`. Итог: **76 инструментов, 218 тестов (141 unit + 77 integration).** Следующий шаг: радиальные/угловые размеры, рамка/формат.
|