feat: параметрика — переменные модели (create/set/delete_variable) через API5

- VariableService: CreateVariableAsync/SetVariableAsync/DeleteVariableAsync/GetVariableValueAsync
- создание ТОЛЬКО на ksFeature.VariableCollection (ksPart — только внешние); RebuildModel + re-fetch
- Expression — ведущее поле (константа/формула со ссылками); value пересчитывается
- list_variables (ModelInspectionService) исправлен: читает feature-коллекцию (все переменные)
- инструменты create_variable/set_variable/delete_variable (VariableTools) + DI
- интеграционный тест CRUD round-trip (создание, формула width*2, удаление по зависимостям)
- ОГРАНИЧЕНИЕ: переменные драйвят геометрию лишь в параметрической модели (эскизы на литералах — нет)
- 116 тестов; спек docs/superpowers/specs/2026-05-27-parametrics-design.md
This commit is contained in:
2026-05-27 11:14:28 +03:00
parent 7d85eb949b
commit bc52b5d920
6 changed files with 298 additions and 1 deletions
@@ -0,0 +1,73 @@
# Дизайн: параметрика (пакет D) — переменные модели
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (тест зелёный); на ревью (ревьюер — Codex)
## Цель
Дать запись параметрики: **создание, изменение и удаление переменных модели** (`list_variables`
уже читает). Это фундамент управляемых моделей: пользовательские переменные, формулы со ссылками,
внешние переменные для сборок.
## Сигнатуры (рефлексия interop + справка)
```
ksVariableCollection:
object AddNewVariable(string name, double value, string note) // создать (ТОЛЬКО на ksFeature!)
object GetByName(string name, bool testFullName, bool testIgnoreCase)
bool RemoveVariable(string name)
int GetCount(); object GetByIndex(int)
ksVariable:
string name; string Expression; double value; bool external; bool Information; string note
ksPart: bool RebuildModel() // применить изменения переменных
ksFeature: object VariableCollection // СВОЙСТВО (не метод), все переменные детали
```
## Ключевые факты (исследование + эмпирика spike)
1. **Создавать переменные можно только на коллекции корневого `ksFeature`**
(`ksPart.GetFeature().VariableCollection`), НЕ на `ksPart.VariableCollection()` (та — только
внешние). `AddNewVariable` на ksPart-коллекции не создаёт пользовательскую переменную.
2. **`Expression` — ведущее поле**, `value` — вычисленный результат. Менять значение надо через
`Expression` (константа «30» или формула «width*2»); прямое `value=` ненадёжно (перезапишется).
3. **Изменения вступают в силу после `ksPart.RebuildModel()`**; `value` пересчитывается из
`Expression`. RCW переменной после RebuildModel «застревает» на старом значении — **перечитывать**
переменную из свежей коллекции (`ReadValueFresh`).
4. **`list_variables` исправлен**: читал `ksPart.VariableCollection()` (только внешние) → теперь
`ksPart.GetFeature().VariableCollection` (все, включая созданные), с фолбэком. Иначе созданная
переменная не была бы видна агенту.
5. **Удаление**: нельзя удалить переменную, на которую ссылаются другие (RRemoveVariable→FALSE);
удалять в порядке зависимостей.
6. **ОГРАНИЧЕНИЕ (важно для контракта):** переменная управляет геометрией только в
**параметрической** модели (размеры эскизов связаны с именами переменных). Наши эскизы строятся
литеральными координатами — у них нет именованных размеров, поэтому set_variable хранит/считает
значение, но геометрию не меняет. Привязка размеров эскиза к переменным (параметрические эскизы)
— отдельный большой пласт (будущее). Поэтому тест проверяет CRUD/вычисление через чтение, а не
изменение геометрии.
## MCP-инструменты (новая группа, `VariableTools`)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `create_variable` | `name`, `value`, `note?`, `external=false` | Создать переменную. Возвращает значение. |
| `set_variable` | `name`, `expression` | Задать выражение (константа/формула), перестроить, вернуть значение. |
| `delete_variable` | `name` | Удалить (нельзя, если есть зависимые). |
Чтение — существующий `list_variables` (теперь видит все переменные).
## Реализация
- **`src/Kompas.Mcp.Core/Modeling/VariableService.cs`** (новый): `CreateVariableAsync`,
`SetVariableAsync`, `DeleteVariableAsync`, `GetVariableValueAsync` (read-back) на STA-потоке.
Хелпер `RootVariables()` → (`ksPart`, feature-коллекция); `ReadValueFresh(name)` после RebuildModel.
Валидация: имя не пусто, value конечно, выражение не пусто.
- **`VariableTools.cs`** (новый): 3 инструмента; DI `AddSingleton<VariableService>()`.
- **`ModelInspectionService.ReadVariables`**: источник → feature-коллекция (фолбэк на ksPart).
- Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
## Тестирование (Integration, `VariableTests`)
CRUD round-trip: построить коробку (дерево построения) → `create_variable("width",40)` (значение 40;
видна в `list_variables`) → `set_variable("width","12+8")` → 20 → создать `height`, `set "width*2"`
40 (формула со ссылкой) → удалить `height`, затем `width` (порядок зависимостей) → обе исчезли
(`GetVariableValueAsync` бросает `KeyNotFoundException`). ✓