Merge feat/parametrics: параметрика — переменные модели (пакет D начат)
3 инструмента: create_variable, set_variable, delete_variable (API5 ksVariable). Создание на ksFeature-коллекции; Expression ведущее; RebuildModel+re-fetch. list_variables починен (видит все переменные). 67 инструментов, 116 тестов. Ограничение: переменные драйвят геометрию лишь в параметрических эскизах (впереди).
This commit is contained in:
@@ -25,7 +25,7 @@ The base is committed to the repo (canonical) — no regeneration step. It cover
|
||||
|
||||
**v1 + v2 + STEP/assembly + direct B-rep edit + structural model inspection implemented and working** (full sketch→feature→inspect→STEP round-trip + move_face + describe_model validated end-to-end). Stack:
|
||||
**.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**.
|
||||
**64 MCP tools, 115 tests green (69 unit + 46 integration).** v2 added: sketch-on-face (by point and by index), revolve (boss/cut + sketch axis), fillet/chamfer (edge-by-point and by index), and Query tools `get_part_info` (МЦХ), `get_bounding_box`, `list_faces`, `list_edges`. After v2: `import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (assembly traversal via `TopPart → IParts7`). `move_face` (`FaceEditService`, `EditTools`) — direct B-rep face editing. After move_face: **structural model inspection** — `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` (`ModelInspectionService`, `InspectionTools`). `describe_model` is the preferred first call over `model_snapshot` (no token cost for image context; gives feature tree, bodies, variables, topology summary, МЦХ). Package A «richer sketches»: 7 new sketch primitives — `sketch_add_arc`, `sketch_add_arc_3points`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`. `PartModeler` refactored into partial classes: `PartModeler.cs` (core), `PartModeler.Sketch.cs` (2D primitives), `PartModeler.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` class in `Core/Modeling`. Package B «forming ops» complete: **`shell`** (оболочка, `o3d_shellOperation=43`) + **`rib`** (ребро жёсткости, `o3d_ribOperation=44`) + **`sweep`** (кинематическая операция, `o3d_baseEvolution=45`) + **`loft`** (по сечениям, `o3d_baseLoft=30`). Auxiliary geometry (start of package E): **`sketch_create_on_offset_plane`** (смещённая плоскость, `o3d_planeOffset=14`) — unlocked `loft`. Additional Edit tools: **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`**. Package C «patterns & mirror» complete: **`linear_pattern`** (линейный массив, `o3d_meshCopy=35`) + **`circular_pattern`** (круговой массив, `o3d_circularCopy=36`) + **`mirror_operation`** (зеркальная копия операций, `o3d_mirrorOperation=48`) + **`mirror_body`** (зеркало тела, `o3d_mirrorAllOperation=49`). **`hole`** (`HoleService`, `FeatureTools`) — цилиндрическое отверстие (сквозное или глухое) через **API7** (`IModelContainer`/`IHole3D`/`IHoleDisposal`); первая формообразующая операция на API7 (прецедент ранее только у `move_face`). **`draft`** (уклон, `o3d_incline=42`) — наклон граней на угол относительно нейтральной координатной плоскости; реализован через **API5** `ksInclineDefinition` (операция называется Incline в API5, не Draft).
|
||||
**67 MCP tools, 116 tests green (69 unit + 47 integration).** v2 added: sketch-on-face (by point and by index), revolve (boss/cut + sketch axis), fillet/chamfer (edge-by-point and by index), and Query tools `get_part_info` (МЦХ), `get_bounding_box`, `list_faces`, `list_edges`. After v2: `import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (assembly traversal via `TopPart → IParts7`). `move_face` (`FaceEditService`, `EditTools`) — direct B-rep face editing. After move_face: **structural model inspection** — `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` (`ModelInspectionService`, `InspectionTools`). `describe_model` is the preferred first call over `model_snapshot` (no token cost for image context; gives feature tree, bodies, variables, topology summary, МЦХ). Package A «richer sketches»: 7 new sketch primitives — `sketch_add_arc`, `sketch_add_arc_3points`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`. `PartModeler` refactored into partial classes: `PartModeler.cs` (core), `PartModeler.Sketch.cs` (2D primitives), `PartModeler.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` class in `Core/Modeling`. Package B «forming ops» complete: **`shell`** (оболочка, `o3d_shellOperation=43`) + **`rib`** (ребро жёсткости, `o3d_ribOperation=44`) + **`sweep`** (кинематическая операция, `o3d_baseEvolution=45`) + **`loft`** (по сечениям, `o3d_baseLoft=30`). Auxiliary geometry (start of package E): **`sketch_create_on_offset_plane`** (смещённая плоскость, `o3d_planeOffset=14`) — unlocked `loft`. Additional Edit tools: **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`**. Package C «patterns & mirror» complete: **`linear_pattern`** (линейный массив, `o3d_meshCopy=35`) + **`circular_pattern`** (круговой массив, `o3d_circularCopy=36`) + **`mirror_operation`** (зеркальная копия операций, `o3d_mirrorOperation=48`) + **`mirror_body`** (зеркало тела, `o3d_mirrorAllOperation=49`). **`hole`** (`HoleService`, `FeatureTools`) — цилиндрическое отверстие (сквозное или глухое) через **API7** (`IModelContainer`/`IHole3D`/`IHoleDisposal`); первая формообразующая операция на API7 (прецедент ранее только у `move_face`). **`draft`** (уклон, `o3d_incline=42`) — наклон граней на угол относительно нейтральной координатной плоскости; реализован через **API5** `ksInclineDefinition` (операция называется Incline в API5, не Draft). Package D «parametrics» started: **`create_variable`**, **`set_variable`**, **`delete_variable`** (`VariableService`, `VariableTools`) — CRUD переменных модели через API5. `list_variables` исправлен (читает `ksPart.GetFeature().VariableCollection`, а не `ksPart.VariableCollection()`). **Ограничение:** переменная управляет геометрией только в параметрической модели (где размеры эскиза связаны с именем переменной); наши эскизы строятся литеральными координатами → `set_variable` хранит/вычисляет значение, но геометрию не меняет. Связь эскизных размеров с переменными (API2D) — не реализована.
|
||||
See [`README.md`](README.md) and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); design decisions/caveats in
|
||||
[`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md).
|
||||
|
||||
@@ -50,11 +50,12 @@ Key implementation facts (don't relearn):
|
||||
- **МЦХ / mass props**: use **API5 `ksPart.CalcMassInertiaProperties(ST_MIX_MM|ST_MIX_KG)`** → `ksMassInertiaParam` (`v`/`m`/`F`/`xc`/`yc`/`zc`), computed on demand. **Do NOT** use API7 `IMassInertiaParam7.Calculate()` — its `Actual` flag sticks after the first calc and won't refresh on API5 geometry changes (stale volume). Bounding box: `ksPart.GetGabarit(full:false, ...)`.
|
||||
- **Snapshot**: `ksDocument3D.SaveAsToRasterFormat(file, ksRasterFormatParam)` renders to a temp file (the in-memory `resultArrayBytes` stays empty when a filename is given); read bytes back. Return to MCP via `ImageContentBlock.FromBytes(bytes, mime)` — **not** `Data = bytes` (Data holds base64 bytes). Use `model_snapshot` only for visually-spatial questions; **prefer `describe_model` first**.
|
||||
- Logs go to **stderr** (stdout is the MCP channel). Integration tests reuse one КОМПАС via `KompasFixture`; artifacts land in gitignored `.scratch/`.
|
||||
- **Structural model inspection** (`ModelInspectionService`, `InspectionTools`): `describe_model` — one-call structural "passport" (bounding box + МЦХ + bodies + topology summary + feature tree + variables); preferred over snapshot. `list_features` — feature tree with params (depth/radius/legs). `list_bodies` — list solid/surface bodies with face count. `list_variables` — model variables (name/expression/value, external/info flags). `describe_face` — drill-down by index (type, area, normal, radius, edge count). `describe_edge` — drill-down by index (type, length, adjacent faces, vertices). `measure` — distance/angle between two objects (face|edge|vertex by index, unit `ST_MIX_MM`).
|
||||
- **Structural model inspection** (`ModelInspectionService`, `InspectionTools`): `describe_model` — one-call structural "passport" (bounding box + МЦХ + bodies + topology summary + feature tree + variables); preferred over snapshot. `list_features` — feature tree with params (depth/radius/legs). `list_bodies` — list solid/surface bodies with face count. `list_variables` — model variables (name/expression/value, external/info flags); reads via `ksPart.GetFeature().VariableCollection` (all variables, including user-created) with fallback to `ksPart.VariableCollection()` (external-only — bug was fixed). `describe_face` — drill-down by index (type, area, normal, radius, edge count). `describe_edge` — drill-down by index (type, length, adjacent faces, vertices). `measure` — distance/angle between two objects (face|edge|vertex by index, unit `ST_MIX_MM`).
|
||||
- **Variable management (package D start, `VariableService`, `VariableTools`)**: `create_variable(name, value, note?, external?)` — creates a model variable; `set_variable(name, expression)` — sets expression (constant `"30"` or formula `"width*2+5"`); `delete_variable(name)` — deletes (fails if dependents exist). **Key API facts (don't relearn):** variable creation MUST use `ksPart.GetFeature().VariableCollection` (property on root ksFeature) — NOT `ksPart.VariableCollection()` (returns external-only). Expression is the leading field; value is derived. Changes applied via `ksPart.RebuildModel()` (return checked); after rebuild the RCW variable is stale → re-read via fresh collection (`ReadValueFresh`). Dependent variables recalculate automatically. Deletion fails if dependents exist (RemoveVariable returns FALSE). **Geometry limitation:** variables control geometry only in a parametric model where sketch dimensions are linked to variable names; our sketches are built with literal coordinates → `set_variable` stores/computes the value but does NOT change geometry. Linking sketch dimensions to variables (parametric sketches via API2D) is not yet implemented.
|
||||
- **Feature tree API** (don't relearn): read via API5 `ksPart.GetFeature()` → `(ksFeature).SubFeatureCollection(true,false)` → `ksFeatureCollection`. Cast `(ksFeature)part` does NOT work (RCW QI returns null) — must use `GetFeature()`. `ksFeature.type` returns only coarse `o3d_entity` and does NOT distinguish operations; precise type and params come from `ksFeature.GetObject()` → `ksEntity.GetDefinition()` and matching definition type (`ksBossExtrusionDefinition.GetSideParam`, `ksFilletDefinition.radius`, `ksChamferDefinition.GetChamferParam`, …). Node name ("Элемент выдавливания:1", "Скругление:1") is localized by КОМПАС itself.
|
||||
- **STEP import/export, `list_components`, `move_face`, `split_solid_by_plane`, `move_body`, `boolean_union`, full inspection layer, richer sketch primitives (package A), `shell`, `rib`, `sweep`, `loft` (package B complete), `sketch_create_on_offset_plane` (package E start), `linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body` (package C complete), `hole` (API7), `draft` (API5 `ksInclineDefinition`) are done.** Not yet done: parametrics (package D), 2D drawing, assembly building; package E continuation (axis, angle plane). Known caveat: boss/cut direction on a selected face depends on face-normal orientation (`forward` may need flipping — agent picks via snapshot or `describe_face` normal). See `docs/OPEN_QUESTIONS.md` → «Ревью v2».
|
||||
- **STEP import/export, `list_components`, `move_face`, `split_solid_by_plane`, `move_body`, `boolean_union`, full inspection layer, richer sketch primitives (package A), `shell`, `rib`, `sweep`, `loft` (package B complete), `sketch_create_on_offset_plane` (package E start), `linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body` (package C complete), `hole` (API7), `draft` (API5 `ksInclineDefinition`), `create_variable`/`set_variable`/`delete_variable` (package D start) are done.** Not yet done: parametric sketches (link sketch dimensions to variable names via API2D — package D continuation), 2D drawing, assembly building; package E continuation (axis, angle plane). Known caveat: boss/cut direction on a selected face depends on face-normal orientation (`forward` may need flipping — agent picks via snapshot or `describe_face` normal). See `docs/OPEN_QUESTIONS.md` → «Ревью v2».
|
||||
- **"STEP import without history"** detected structurally: `bodyCount > 0 && no formative features && features.Count <= bodyCount+1` (only origin + body). STEP with edits (move_face: "Смещённая плоскость", "Разрезать", "Переместить грани", "Булева операция") — that already has history.
|
||||
- **Topology/measure API**: `ksFaceDefinition.EdgeCollection` / `GetCylinderParam` / `GetSurface().GetNormal` + `normalOrientation`; `ksEdgeDefinition.GetAdjacentFace(bool)` / `GetVertex(bool)` → `ksVertexDefinition.GetPoint`; bodies via `ksPart.BodyCollection()` → `ksBody` (`IsSolid` / `FaceCollection`); variables via `ksPart.VariableCollection()` → `ksVariable`; measurements via `ksPart.GetMeasurer()` → `ksMeasurer` (`SetObject1/2`, `unit=ST_MIX_MM`, `Calc`, `distance`/`MinDistance`/`angle` in degrees / `IsAngleValid`).
|
||||
- **Topology/measure API**: `ksFaceDefinition.EdgeCollection` / `GetCylinderParam` / `GetSurface().GetNormal` + `normalOrientation`; `ksEdgeDefinition.GetAdjacentFace(bool)` / `GetVertex(bool)` → `ksVertexDefinition.GetPoint`; bodies via `ksPart.BodyCollection()` → `ksBody` (`IsSolid` / `FaceCollection`); all variables via `ksPart.GetFeature().VariableCollection` → `ksVariable` (external-only: `ksPart.VariableCollection()` — do NOT use for reading all variables); measurements via `ksPart.GetMeasurer()` → `ksMeasurer` (`SetObject1/2`, `unit=ST_MIX_MM`, `Calc`, `distance`/`MinDistance`/`angle` in degrees / `IsAngleValid`).
|
||||
- **STEP import COM pattern** (verified): `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (pass format code, not DLL path) → `IConverter.ConverterParameters(cmd)` → set `IAdditionConvertParameters.Format=ksConverterFromSTEP` → `IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`. Export: `ConvertToAdditionFormat` with format codes for AP203/AP214/AP242. Param co-classes (AdditionConvertParameters etc.) are NOT CoCreatable — only via converter factories. Assembly traversal: `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`. Extract component: `prm.NeedCreateComponentsFiles=true` (writes .m3d files next to STEP) + `IPart7.OpenSourceDocument`.
|
||||
- **Direct B-rep face editing (`move_face`)** (verified on top_spacer, 39.45→41.45 mm height): API7 face object retrieved via `IPart7.FindObjectsByPoint(x,y,z,true)` → cast to `KompasAPI7.IFace`; containers acquired at runtime via COM-QI: `(ISurfaceContainer)part`, `(IModelContainer)part`; `FaceMover` (SetFaces + Offset + Direction + Update) moves the face. distance>0 = outward (add material), <0 = inward. Works on both parametric and imported B-rep geometry. Full split-reposition workflow (SplitSolids/BodyRepositions) is driveable but not yet exposed as tools.
|
||||
- **Shell operation (`shell`, package B)**: `ksShellDefinition` via `ksPart.NewEntity(o3d_shellOperation=43)`; `thickness` (mm) + `thinType` (bool: `true`=inward/`false`=outward; mapping: `thinType = !outward`) + `FaceArray()` (open faces, ≥1, by index from `list_faces`). Helper `SelectFaceByIndex` in `PartModeler.cs` (analogous to `SelectEdgeByIndex`, with range check). Validation: `thickness>0`, dedup via `Distinct`, range check before mutation. Transient RCWs in `ShellAsync`/`SelectFaceByIndex` are intentionally not released (consistent with Fillet/Chamfer pattern).
|
||||
|
||||
@@ -39,7 +39,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
}
|
||||
```
|
||||
|
||||
## Инструменты (64 инструмента)
|
||||
## Инструменты (67 инструментов)
|
||||
|
||||
| Группа | Инструменты |
|
||||
|---|---|
|
||||
@@ -51,6 +51,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
| Inspection | `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` |
|
||||
| Vision | `model_snapshot` (PNG-снимок; fallback — сначала `describe_model`) |
|
||||
| Query | `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`, `list_components` |
|
||||
| Variables | `create_variable`, `set_variable`, `delete_variable` |
|
||||
| Conversion | `import_step`, `export_step` |
|
||||
| Validation | `validate_part` |
|
||||
|
||||
@@ -70,7 +71,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
```
|
||||
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции, конвертация, снимок, инспекция модели
|
||||
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов
|
||||
tests/Kompas.Mcp.Tests/ 115 тестов: unit + integration (integration требуют КОМПАС)
|
||||
tests/Kompas.Mcp.Tests/ 116 тестов: unit + integration (integration требуют КОМПАС)
|
||||
libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common)
|
||||
docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/)
|
||||
usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d
|
||||
@@ -89,7 +90,7 @@ dotnet test --filter "Category=Integration" # только integration
|
||||
|
||||
- **STA-поток** (`KompasDispatcher`) владеет всеми COM-вызовами — КОМПАС однопоточный STA.
|
||||
- Подключение через API5 `KompasObject` (`KOMPAS.Application.5`) → API7 `IApplication` (`ksGetApplication7`).
|
||||
- 3D строится через API5 `ksPart` (эскизы, выдавливание, вращение, скругление/фаска); снимок — `ksDocument3D.SaveAsToRasterFormat`. Прямое редактирование импортированной B-rep (`move_face`) — через API7 `IPart7.FindObjectsByPoint` + `FaceMover`. Структурный осмотр (`describe_model` и др.) — через `ModelInspectionService` (дерево операций via `ksPart.GetFeature()`, тела via `BodyCollection()`, переменные via `VariableCollection()`, измерения via `ksMeasurer`).
|
||||
- 3D строится через API5 `ksPart` (эскизы, выдавливание, вращение, скругление/фаска); снимок — `ksDocument3D.SaveAsToRasterFormat`. Прямое редактирование импортированной B-rep (`move_face`) — через API7 `IPart7.FindObjectsByPoint` + `FaceMover`. Структурный осмотр (`describe_model` и др.) — через `ModelInspectionService` (дерево операций via `ksPart.GetFeature()`, тела via `BodyCollection()`, переменные via `GetFeature().VariableCollection`, измерения via `ksMeasurer`). Управление переменными (`create_variable`, `set_variable`, `delete_variable`) — через `VariableService` (API5).
|
||||
- STEP импорт/экспорт — `IKompasDocument3D1.ConvertFromAdditionFormat`/`ConvertToAdditionFormat` через встроенный конвертер.
|
||||
- MCP — официальный C# SDK `ModelContextProtocol`, транспорт stdio, логи в stderr.
|
||||
|
||||
|
||||
+23
-11
@@ -1,6 +1,6 @@
|
||||
# Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант)
|
||||
|
||||
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-E «offset plane»+`hole`+`draft`). Актуализировано по коду.
|
||||
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-D start «переменные»+package-E «offset plane»+`hole`+`draft`). Актуализировано по коду.
|
||||
> Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md).
|
||||
> Имена интерфейсов и перечислений сверены со справкой SDK по MD-базе знаний `docs/Kompas3D_SDK/` (навык `kompas-sdk-research`).
|
||||
|
||||
@@ -113,7 +113,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|---|---|
|
||||
| **Kompas.Mcp.Host** | Точка входа. Сборка MCP-сервера (stdio), DI (в т.ч. `ConversionService`), логи в stderr, старт и владение STA-потоком, graceful shutdown. Определения инструментов по категориям (System / Documents / Sketch / Features / Query / Conversion). **Тонкий слой**: валидация аргументов → постановка задачи на STA-поток → форматирование результата/ошибки в MCP-ответ. |
|
||||
| **Kompas.Mcp.Core** | COM-слой. Менеджер соединения, менеджер документов, построитель эскизов и операций, `ConversionService` (STEP импорт/экспорт), `QueryService` (МЦХ, грани, рёбра, компоненты), `ModelInspectionService` (дерево операций, тела, переменные, drill-down, измерения), снимок. Все паттерны API5/API7 живут здесь. |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 115 тестов (69 unit + 46 integration). |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 116 тестов (69 unit + 47 integration). |
|
||||
|
||||
Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
|
||||
|
||||
@@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
---
|
||||
|
||||
## 5. Карта инструментов (64 инструмента)
|
||||
## 5. Карта инструментов (67 инструментов)
|
||||
|
||||
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`.
|
||||
|
||||
@@ -187,9 +187,20 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
Реализовано через `ModelInspectionService` (`src/Kompas.Mcp.Core/Query/ModelInspectionService.cs`),
|
||||
рендер текста — `InspectionText` (`InspectionText.cs`, юнит-тестируем без COM).
|
||||
Ключевые API-паттерны: `ksPart.GetFeature()` → `ksFeatureCollection` (дерево); `BodyCollection()` → `ksBody`; `VariableCollection()` → `ksVariable`; `GetMeasurer()` → `ksMeasurer`.
|
||||
Ключевые API-паттерны: `ksPart.GetFeature()` → `ksFeatureCollection` (дерево); `BodyCollection()` → `ksBody`; `ksPart.GetFeature().VariableCollection` → `ksVariable` (все переменные; `ksPart.VariableCollection()` — только внешние); `GetMeasurer()` → `ksMeasurer`.
|
||||
«Импорт STEP без истории» определяется структурно: `bodyCount > 0 && нет формообразующих операций && features.Count <= bodyCount+1`.
|
||||
|
||||
**Variables — управление переменными модели (package D start)**
|
||||
- `create_variable` — создать переменную (имя, начальное значение, примечание, флаг внешней).
|
||||
- `set_variable` — задать выражение (константа `"30"` или формула `"width*2+5"`); десятичный разделитель — точка; модель перестраивается, зависимые переменные пересчитываются.
|
||||
- `delete_variable` — удалить переменную (нельзя при наличии зависимых).
|
||||
|
||||
Реализовано через `VariableService` (`src/Kompas.Mcp.Core/Modeling/VariableService.cs`, API5), инструменты — `VariableTools` (`src/Kompas.Mcp.Host/Tools/VariableTools.cs`). DI: `AddSingleton<VariableService>()` в `Program.cs`.
|
||||
|
||||
Ключевые API-паттерны: создание — ТОЛЬКО через `ksPart.GetFeature().VariableCollection` (свойство корневого `ksFeature`), а НЕ `ksPart.VariableCollection()` (та возвращает только внешние); Expression — ведущее поле, Value — вычисленный результат; изменение через Expression + `ksPart.RebuildModel()`; после rebuild RCW переменной стал; значение перечитывается через свежую коллекцию (`ReadValueFresh`).
|
||||
|
||||
**Ограничение:** переменная управляет геометрией только в параметрической модели, где размеры эскизов привязаны к именам переменных. Наши эскизы строятся литеральными координатами — `set_variable` хранит и вычисляет значение, но геометрию не меняет. Связь размеров эскизов с переменными (параметрические эскизы через API2D) — не реализована (package D продолжение).
|
||||
|
||||
**Vision — визуальная обратная связь (fallback)**
|
||||
- `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**,
|
||||
чтобы мультимодальный агент *увидел* промежуточный результат. Использовать для визуально-пространственных
|
||||
@@ -323,15 +334,16 @@ dotnet build -c Release -r win-x64 # сборка
|
||||
|
||||
## 10. Дорожная карта
|
||||
|
||||
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-E start+hole+draft):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели).
|
||||
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+draft):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели); **`create_variable`, `set_variable`, `delete_variable`** (управление переменными, package D start).
|
||||
|
||||
**Следующие приоритеты:**
|
||||
1. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу.
|
||||
4. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
|
||||
5. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
|
||||
6. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты.
|
||||
7. Спецификации (`ISpecification`), построение сборок и сопряжения.
|
||||
8. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
|
||||
1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D).
|
||||
2. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу.
|
||||
3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
|
||||
4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
|
||||
5. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты.
|
||||
6. Спецификации (`ISpecification`), построение сборок и сопряжения.
|
||||
7. Транспорт **HTTP/SSE** — как опция для удалённых клиентов.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -160,3 +160,16 @@ stateful-сессии? Пока полагаемся на последовате
|
||||
**Ключевая тонкость (эмпирика):** `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.
|
||||
|
||||
+11
-9
@@ -98,7 +98,7 @@
|
||||
.progrow{display:flex;justify-content:space-between;font-size:13px;color:var(--muted);margin-bottom:8px}
|
||||
|
||||
/* roadmap timeline */
|
||||
.timeline{display:grid;grid-template-columns:repeat(8,1fr);gap:14px;margin-top:8px}
|
||||
.timeline{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:14px;margin-top:8px}
|
||||
.tl{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px 16px;position:relative}
|
||||
.tl .ph{font-size:12px;font-weight:700;color:var(--accent)}
|
||||
.tl h4{font-size:15px;margin:8px 0 6px}
|
||||
@@ -111,7 +111,7 @@
|
||||
.links{display:flex;gap:20px;flex-wrap:wrap;margin-top:10px}
|
||||
|
||||
@media(max-width:1020px){
|
||||
.timeline{grid-template-columns:repeat(4,1fr)}
|
||||
.timeline{grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}
|
||||
}
|
||||
@media(max-width:880px){
|
||||
.grid,.stack{grid-template-columns:1fr 1fr}
|
||||
@@ -128,7 +128,7 @@
|
||||
<!-- HERO -->
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+draft (64 инструмента)</span>
|
||||
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+draft+переменные (67 инструментов)</span>
|
||||
<h1>КОМПАС-3D <span class="g">MCP-сервер</span><br>управление CAD языком LLM</h1>
|
||||
<p class="lead">MCP-сервер, который превращает операции КОМПАС-3D — создание документов,
|
||||
эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом —
|
||||
@@ -261,8 +261,8 @@
|
||||
впереди.</p>
|
||||
|
||||
<div class="prog">
|
||||
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · offset-plane (пакет E start) · hole (API7) · draft (API5) · навык kompas-3d · 64 инструмента · 115 тестов</span></div>
|
||||
<div class="bar"><i style="width:96%"></i></div>
|
||||
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · draft (API5) · навык kompas-3d · 67 инструментов · 116 тестов</span></div>
|
||||
<div class="bar"><i style="width:97%"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="todo-grid" style="margin-top:26px">
|
||||
@@ -297,7 +297,7 @@
|
||||
<!-- TOOLS -->
|
||||
<div class="panel">
|
||||
<h3>Инструменты v3+ <span class="tag done">ГОТОВО</span></h3>
|
||||
<p class="meta">64 инструмента, отдаются по MCP-протоколу</p>
|
||||
<p class="meta">67 инструментов, отдаются по MCP-протоколу</p>
|
||||
<ul class="list">
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>System</code>: connect · status · set_visible</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Documents</code>: create · open · save · save_as · close · active</span></li>
|
||||
@@ -307,8 +307,9 @@
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Inspection</code>: describe_model · list_features · list_bodies · list_variables · describe_face · describe_edge · measure</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Vision</code>: model_snapshot 👁️ (fallback)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Query</code>: get_part_info · get_bounding_box · list_faces · list_edges · list_components</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Variables</code>: <b>create_variable</b> · <b>set_variable</b> · <b>delete_variable</b> (пакет D)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Conversion</code>: import_step · export_step</span></li>
|
||||
<li><span class="mark wip">→</span><span>пакет E продолжение (ось, угловая плоскость)</span></li>
|
||||
<li><span class="mark wip">→</span><span>пакет D продолжение (параметрические эскизы, API2D) · пакет E (ось, угловая плоскость)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -319,7 +320,7 @@
|
||||
<ul class="list">
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 115 тестов зелёных (69 unit + 46 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 116 тестов зелёных (69 unit + 47 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Сборка <code>kompas-mcp.exe</code>, README с конфигом клиента</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Навык <code>kompas-3d</code> (методика) + полигон <code>usecases/</code></span></li>
|
||||
<li><span class="mark todo"></span><span>In-process тест MCP-клиента, релизный publish</span></li>
|
||||
@@ -344,7 +345,8 @@
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет B · готово ✓</div><h4>Формообразующие операции</h4><p><b>shell</b> ✓ · <b>rib</b> ✓ · <b>sweep</b> ✓ · <b>loft</b> ✓ (по сечениям) · <b>offset-plane</b> ✓ (пакет E)</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет C · готово ✓</div><h4>Массивы и зеркало</h4><p><b>linear_pattern</b> ✓ · <b>circular_pattern</b> ✓ · <b>mirror_operation</b> ✓ · <b>mirror_body</b> ✓ · <b>hole</b> ✓ (API7)</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">draft · готово ✓</div><h4>Уклон</h4><p><b>draft</b> ✓ (API5 <code>ksInclineDefinition</code>, o3d_incline=42) · 64 инструмента · 115 тестов</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе</div><h4>пакет E продолжение</h4><p>вспомогательная геометрия: ось, точка, угловая плоскость</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет D start · готово ✓</div><h4>Переменные модели</h4><p><b>create_variable</b> · <b>set_variable</b> · <b>delete_variable</b> (API5 VariableService) · list_variables исправлен · 67 инструментов · 116 тестов</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе</div><h4>пакет D продолжение + пакет E</h4><p>параметрические эскизы (API2D) · вспомогательная геометрия (ось, угловая плоскость)</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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`). ✓
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.Versioning;
|
||||
using Kompas.Mcp.Core.Threading;
|
||||
using Kompas6API5;
|
||||
using Kompas6Constants3D;
|
||||
|
||||
namespace Kompas.Mcp.Core.Modeling;
|
||||
|
||||
/// <summary>
|
||||
/// Параметрика: создание и изменение переменных модели (API5 <c>ksVariable</c>).
|
||||
/// Создание возможно только на коллекции переменных корневого <c>ksFeature</c> (не <c>ksPart</c>).
|
||||
/// <para>ВАЖНО: переменная управляет геометрией лишь в параметрической модели (где размеры
|
||||
/// эскизов связаны с именованными переменными). Эскизы, построенные литеральными координатами,
|
||||
/// не имеют именованных размеров — для них переменные хранятся, но геометрию не меняют.</para>
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public sealed class VariableService
|
||||
{
|
||||
private readonly KompasSession _session;
|
||||
private readonly KompasDispatcher _dispatcher;
|
||||
|
||||
public VariableService(KompasSession session, KompasDispatcher dispatcher)
|
||||
{
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
/// <summary>Создать новую переменную модели с начальным значением. external=true — внешняя
|
||||
/// (доступна для управления из сборки). Возвращает вычисленное значение.</summary>
|
||||
public Task<double> CreateVariableAsync(string name, double value, string? note = null, bool external = false, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RequireName(name);
|
||||
if (!double.IsFinite(value)) throw new ArgumentException("Значение должно быть конечным.", nameof(value));
|
||||
var (part, vc) = RootVariables();
|
||||
if (vc.GetByName(name, true, false) is ksVariable)
|
||||
throw new InvalidOperationException($"Переменная '{name}' уже существует.");
|
||||
var v = vc.AddNewVariable(name, value, note ?? "") as ksVariable
|
||||
?? throw new InvalidOperationException("AddNewVariable вернул null (недопустимое имя?).");
|
||||
if (external) v.external = true;
|
||||
if (!part.RebuildModel())
|
||||
throw new InvalidOperationException($"Не удалось применить создание переменной '{name}' (RebuildModel вернул FALSE).");
|
||||
return ReadValueFresh(name); // значение перечитываем после перестроения
|
||||
}, ct);
|
||||
|
||||
/// <summary>Задать выражение переменной (константа «30» или формула «len*2+5») и перестроить.
|
||||
/// Возвращает вычисленное значение.</summary>
|
||||
public Task<double> SetVariableAsync(string name, string expression, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RequireName(name);
|
||||
if (string.IsNullOrWhiteSpace(expression))
|
||||
throw new ArgumentException("Выражение не может быть пустым.", nameof(expression));
|
||||
var (part, vc) = RootVariables();
|
||||
var v = RequireVariable(vc, name);
|
||||
v.Expression = expression;
|
||||
if (!part.RebuildModel())
|
||||
throw new InvalidOperationException(
|
||||
$"Не удалось пересчитать модель с выражением «{expression}» для '{name}' (недопустимое выражение или ссылка на несуществующую переменную?).");
|
||||
return ReadValueFresh(name); // переменную перечитываем после перестроения (value пересчитан)
|
||||
}, ct);
|
||||
|
||||
/// <summary>Удалить переменную модели.</summary>
|
||||
public Task DeleteVariableAsync(string name, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RequireName(name);
|
||||
var (part, vc) = RootVariables();
|
||||
RequireVariable(vc, name); // понятная ошибка, если её нет
|
||||
if (!vc.RemoveVariable(name))
|
||||
throw new InvalidOperationException(
|
||||
$"Не удалось удалить переменную '{name}' (возможно, на неё ссылаются другие переменные — удалите зависимые сначала).");
|
||||
if (!part.RebuildModel())
|
||||
throw new InvalidOperationException($"Не удалось применить удаление переменной '{name}' (RebuildModel вернул FALSE).");
|
||||
}, ct);
|
||||
|
||||
/// <summary>Прочитать текущее (вычисленное) значение переменной.</summary>
|
||||
public Task<double> GetVariableValueAsync(string name, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RequireName(name);
|
||||
var (_, vc) = RootVariables();
|
||||
return RequireVariable(vc, name).value;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Перечитать значение переменной из свежей коллекции (после RebuildModel оно пересчитано).</summary>
|
||||
private double ReadValueFresh(string name)
|
||||
{
|
||||
var (_, vc) = RootVariables();
|
||||
return RequireVariable(vc, name).value;
|
||||
}
|
||||
|
||||
private static void RequireName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("Имя переменной не может быть пустым.", nameof(name));
|
||||
}
|
||||
|
||||
private static ksVariable RequireVariable(ksVariableCollection vc, string name)
|
||||
=> vc.GetByName(name, true, false) as ksVariable
|
||||
?? throw new KeyNotFoundException($"Переменная '{name}' не найдена.");
|
||||
|
||||
/// <summary>Корневой ksFeature детали и его коллекция переменных (на ней работает AddNewVariable).</summary>
|
||||
private (ksPart part, ksVariableCollection vc) RootVariables()
|
||||
{
|
||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
||||
var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
||||
var feature = part.GetFeature() as ksFeature
|
||||
?? throw new InvalidOperationException("GetFeature() вернул null (нет дерева построения).");
|
||||
var vc = feature.VariableCollection as ksVariableCollection
|
||||
?? throw new InvalidOperationException("VariableCollection вернул не ksVariableCollection.");
|
||||
return (part, vc);
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,11 @@ public sealed class ModelInspectionService
|
||||
|
||||
private static IReadOnlyList<VariableInfo> ReadVariables(ksPart part)
|
||||
{
|
||||
if (part.VariableCollection() is not ksVariableCollection vc) return Array.Empty<VariableInfo>();
|
||||
// Переменные детали живут в коллекции корневого ksFeature; ksPart.VariableCollection() даёт
|
||||
// только внешние. Берём feature-коллекцию (все переменные, включая созданные), с фолбэком.
|
||||
var vc = (part.GetFeature() as ksFeature)?.VariableCollection as ksVariableCollection
|
||||
?? part.VariableCollection() as ksVariableCollection;
|
||||
if (vc is null) return Array.Empty<VariableInfo>();
|
||||
int n = vc.GetCount();
|
||||
var list = new List<VariableInfo>(n);
|
||||
for (int i = 0; i < n; i++)
|
||||
|
||||
@@ -23,6 +23,7 @@ builder.Services.AddSingleton<KompasSession>();
|
||||
builder.Services.AddSingleton<DocumentService>();
|
||||
builder.Services.AddSingleton<PartModeler>();
|
||||
builder.Services.AddSingleton<HoleService>();
|
||||
builder.Services.AddSingleton<VariableService>();
|
||||
builder.Services.AddSingleton<SnapshotService>();
|
||||
builder.Services.AddSingleton<QueryService>();
|
||||
builder.Services.AddSingleton<ModelInspectionService>();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.ComponentModel;
|
||||
using Kompas.Mcp.Core;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace Kompas.Mcp.Host.Tools;
|
||||
|
||||
/// <summary>Инструменты параметрики: переменные модели (создание, изменение, удаление).</summary>
|
||||
[McpServerToolType]
|
||||
public sealed class VariableTools(KompasSession session, VariableService variables)
|
||||
{
|
||||
[McpServerTool(Name = "create_variable")]
|
||||
[Description("Создать переменную модели с начальным числовым значением value. note — комментарий. external=true — внешняя (доступна для управления из сборки). Имя должно быть уникальным. Примечание: переменная управляет геометрией только в параметрической модели (где размеры эскизов связаны с именами переменных); для моделей с литеральными размерами переменная просто хранится. Используйте list_variables для просмотра. Возвращает вычисленное значение.")]
|
||||
public async Task<string> CreateVariable(
|
||||
string name,
|
||||
[Description("Начальное числовое значение")] double value,
|
||||
[Description("Комментарий (необязательно)")] string? note = null,
|
||||
[Description("Внешняя переменная (управляема из сборки)")] bool external = false)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var v = await variables.CreateVariableAsync(name, value, note, external);
|
||||
return $"Переменная '{name}' создана (значение {v}).";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "set_variable")]
|
||||
[Description("Задать выражение существующей переменной: константа (например «30») или формула со ссылками на другие переменные (например «width*2+5»). Модель перестраивается, значение пересчитывается. Десятичный разделитель — точка. Примечание: изменит геометрию только в параметрической модели (где размеры эскизов связаны с именами переменных); для моделей с литеральными размерами значение пересчитается, но геометрия не изменится. Возвращает вычисленное значение.")]
|
||||
public async Task<string> SetVariable(
|
||||
string name,
|
||||
[Description("Выражение: число или формула (width*2+5)")] string expression)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var v = await variables.SetVariableAsync(name, expression);
|
||||
return $"Переменная '{name}' = «{expression}» → {v}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "delete_variable")]
|
||||
[Description("Удалить переменную модели по имени. Нельзя удалить переменную, на которую ссылаются другие (сначала удалите зависимые).")]
|
||||
public async Task<string> DeleteVariable(string name)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
await variables.DeleteVariableAsync(name);
|
||||
return $"Переменная '{name}' удалена.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
using Kompas.Mcp.Core.Query;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: переменные модели (параметрика, API5 ksVariable).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class VariableTests
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly VariableService _vars;
|
||||
private readonly ModelInspectionService _inspect;
|
||||
|
||||
public VariableTests(KompasFixture fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
_vars = new VariableService(fx.Session, fx.Dispatcher);
|
||||
_inspect = new ModelInspectionService(fx.Session, fx.Dispatcher);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Variable_crud_roundtrip()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Небольшая геометрия, чтобы было дерево построения.
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddRectangleAsync(s, 0, 0, 20, 20);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 10);
|
||||
|
||||
// create
|
||||
var created = await _vars.CreateVariableAsync("width", 40, "ширина");
|
||||
Assert.Equal(40, created, 3);
|
||||
Assert.Equal(40, await _vars.GetVariableValueAsync("width"), 3);
|
||||
|
||||
// видна в list_variables (пользовательское чтение)
|
||||
var list = await _inspect.ListVariablesAsync();
|
||||
Assert.Contains(list, v => v.Name == "width");
|
||||
|
||||
// set expression (формула → вычисленное значение)
|
||||
var computed = await _vars.SetVariableAsync("width", "12+8");
|
||||
Assert.Equal(20, computed, 3);
|
||||
Assert.Equal(20, await _vars.GetVariableValueAsync("width"), 3);
|
||||
|
||||
// дубликат имени отвергается
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _vars.CreateVariableAsync("width", 1));
|
||||
|
||||
// вторая переменная, ссылающаяся на первую (формула с именем переменной)
|
||||
await _vars.CreateVariableAsync("height", 5);
|
||||
var doubled = await _vars.SetVariableAsync("height", "width * 2");
|
||||
Assert.Equal(40, doubled, 3); // width=20 → 40
|
||||
|
||||
// десятичный разделитель — точка; зависимая переменная пересчитывается
|
||||
Assert.Equal(3.5, await _vars.SetVariableAsync("width", "3.5"), 3);
|
||||
Assert.Equal(7, await _vars.GetVariableValueAsync("height"), 3); // 3.5*2
|
||||
|
||||
// нельзя удалить переменную, на которую ссылаются (height = width*2)
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _vars.DeleteVariableAsync("width"));
|
||||
|
||||
// delete (в порядке зависимостей: сначала зависимая height, затем width)
|
||||
await _vars.DeleteVariableAsync("height");
|
||||
await _vars.DeleteVariableAsync("width");
|
||||
await Assert.ThrowsAsync<KeyNotFoundException>(() => _vars.GetVariableValueAsync("width"));
|
||||
await Assert.ThrowsAsync<KeyNotFoundException>(() => _vars.GetVariableValueAsync("height"));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user