From bc52b5d920eb907bfdc396aa1dc40cbc15378bb8 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 11:14:28 +0300 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B0=20=E2=80=94=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B8=20(create/set/delete=5Fvariable)=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20API5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../specs/2026-05-27-parametrics-design.md | 73 ++++++++++++ .../Modeling/VariableService.cs | 111 ++++++++++++++++++ .../Query/ModelInspectionService.cs | 6 +- src/Kompas.Mcp.Host/Program.cs | 1 + src/Kompas.Mcp.Host/Tools/VariableTools.cs | 44 +++++++ .../Integration/VariableTests.cs | 64 ++++++++++ 6 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/specs/2026-05-27-parametrics-design.md create mode 100644 src/Kompas.Mcp.Core/Modeling/VariableService.cs create mode 100644 src/Kompas.Mcp.Host/Tools/VariableTools.cs create mode 100644 tests/Kompas.Mcp.Tests/Integration/VariableTests.cs diff --git a/docs/superpowers/specs/2026-05-27-parametrics-design.md b/docs/superpowers/specs/2026-05-27-parametrics-design.md new file mode 100644 index 0000000..00841ee --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-parametrics-design.md @@ -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()`. +- **`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`). ✓ diff --git a/src/Kompas.Mcp.Core/Modeling/VariableService.cs b/src/Kompas.Mcp.Core/Modeling/VariableService.cs new file mode 100644 index 0000000..f810177 --- /dev/null +++ b/src/Kompas.Mcp.Core/Modeling/VariableService.cs @@ -0,0 +1,111 @@ +using System.Globalization; +using System.Runtime.Versioning; +using Kompas.Mcp.Core.Threading; +using Kompas6API5; +using Kompas6Constants3D; + +namespace Kompas.Mcp.Core.Modeling; + +/// +/// Параметрика: создание и изменение переменных модели (API5 ksVariable). +/// Создание возможно только на коллекции переменных корневого ksFeature (не ksPart). +/// ВАЖНО: переменная управляет геометрией лишь в параметрической модели (где размеры +/// эскизов связаны с именованными переменными). Эскизы, построенные литеральными координатами, +/// не имеют именованных размеров — для них переменные хранятся, но геометрию не меняют. +/// +[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)); + } + + /// Создать новую переменную модели с начальным значением. external=true — внешняя + /// (доступна для управления из сборки). Возвращает вычисленное значение. + public Task 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; + part.RebuildModel(); + return ReadValueFresh(name); // значение перечитываем после перестроения + }, ct); + + /// Задать выражение переменной (константа «30» или формула «len*2+5») и перестроить. + /// Возвращает вычисленное значение. + public Task 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; + part.RebuildModel(); + return ReadValueFresh(name); // переменную перечитываем после перестроения (value пересчитан) + }, ct); + + /// Удалить переменную модели. + 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}'."); + part.RebuildModel(); + }, ct); + + /// Прочитать текущее (вычисленное) значение переменной. + public Task GetVariableValueAsync(string name, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + { + RequireName(name); + var (_, vc) = RootVariables(); + return RequireVariable(vc, name).value; + }, ct); + + /// Перечитать значение переменной из свежей коллекции (после RebuildModel оно пересчитано). + 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}' не найдена."); + + /// Корневой ksFeature детали и его коллекция переменных (на ней работает AddNewVariable). + 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); + } +} diff --git a/src/Kompas.Mcp.Core/Query/ModelInspectionService.cs b/src/Kompas.Mcp.Core/Query/ModelInspectionService.cs index 0e565fb..c59c61d 100644 --- a/src/Kompas.Mcp.Core/Query/ModelInspectionService.cs +++ b/src/Kompas.Mcp.Core/Query/ModelInspectionService.cs @@ -260,7 +260,11 @@ public sealed class ModelInspectionService private static IReadOnlyList ReadVariables(ksPart part) { - if (part.VariableCollection() is not ksVariableCollection vc) return Array.Empty(); + // Переменные детали живут в коллекции корневого ksFeature; ksPart.VariableCollection() даёт + // только внешние. Берём feature-коллекцию (все переменные, включая созданные), с фолбэком. + var vc = (part.GetFeature() as ksFeature)?.VariableCollection as ksVariableCollection + ?? part.VariableCollection() as ksVariableCollection; + if (vc is null) return Array.Empty(); int n = vc.GetCount(); var list = new List(n); for (int i = 0; i < n; i++) diff --git a/src/Kompas.Mcp.Host/Program.cs b/src/Kompas.Mcp.Host/Program.cs index 83be864..dd7150d 100644 --- a/src/Kompas.Mcp.Host/Program.cs +++ b/src/Kompas.Mcp.Host/Program.cs @@ -23,6 +23,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Kompas.Mcp.Host/Tools/VariableTools.cs b/src/Kompas.Mcp.Host/Tools/VariableTools.cs new file mode 100644 index 0000000..7f89f2e --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/VariableTools.cs @@ -0,0 +1,44 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Modeling; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Инструменты параметрики: переменные модели (создание, изменение, удаление). +[McpServerToolType] +public sealed class VariableTools(KompasSession session, VariableService variables) +{ + [McpServerTool(Name = "create_variable")] + [Description("Создать переменную модели с начальным числовым значением value. note — комментарий. external=true — внешняя (доступна для управления из сборки). Имя должно быть уникальным. Примечание: переменная управляет геометрией только в параметрической модели (где размеры эскизов связаны с именами переменных); для моделей с литеральными размерами переменная просто хранится. Используйте list_variables для просмотра. Возвращает вычисленное значение.")] + public async Task 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 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 DeleteVariable(string name) + { + await session.ConnectAsync(); + await variables.DeleteVariableAsync(name); + return $"Переменная '{name}' удалена."; + } +} diff --git a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs new file mode 100644 index 0000000..bff3b3f --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs @@ -0,0 +1,64 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Modeling; +using Kompas.Mcp.Core.Query; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: переменные модели (параметрика, API5 ksVariable). +[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 _vars.CreateVariableAsync("height", 5); + var doubled = await _vars.SetVariableAsync("height", "width * 2"); + Assert.Equal(40, doubled, 3); // width=20 → 40 + + // delete (в порядке зависимостей: сначала зависимая height, затем width) + await _vars.DeleteVariableAsync("height"); + await _vars.DeleteVariableAsync("width"); + await Assert.ThrowsAsync(() => _vars.GetVariableValueAsync("width")); + await Assert.ThrowsAsync(() => _vars.GetVariableValueAsync("height")); + } + finally { await _docs.CloseAsync(save: false); } + } +} From a30614d0057c08334779436a6d39f75dc41f4357 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 11:20:38 +0300 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20Codex?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - проверка возврата ksPart.RebuildModel() во всех операциях (не рапортовать успех при неудаче) — P2 - сообщение об удалении: намёк на зависимые переменные — P3 - дисклеймер про геометрию в описании set_variable + десятичный разделитель — P3 - тест дополнен: дубликат имени, удаление с зависимыми, десятичное выражение (3.5), пересчёт зависимой - 116 тестов зелёных --- src/Kompas.Mcp.Core/Modeling/VariableService.cs | 13 +++++++++---- src/Kompas.Mcp.Host/Tools/VariableTools.cs | 2 +- tests/Kompas.Mcp.Tests/Integration/VariableTests.cs | 10 ++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Kompas.Mcp.Core/Modeling/VariableService.cs b/src/Kompas.Mcp.Core/Modeling/VariableService.cs index f810177..02aa2a4 100644 --- a/src/Kompas.Mcp.Core/Modeling/VariableService.cs +++ b/src/Kompas.Mcp.Core/Modeling/VariableService.cs @@ -38,7 +38,8 @@ public sealed class VariableService var v = vc.AddNewVariable(name, value, note ?? "") as ksVariable ?? throw new InvalidOperationException("AddNewVariable вернул null (недопустимое имя?)."); if (external) v.external = true; - part.RebuildModel(); + if (!part.RebuildModel()) + throw new InvalidOperationException($"Не удалось применить создание переменной '{name}' (RebuildModel вернул FALSE)."); return ReadValueFresh(name); // значение перечитываем после перестроения }, ct); @@ -53,7 +54,9 @@ public sealed class VariableService var (part, vc) = RootVariables(); var v = RequireVariable(vc, name); v.Expression = expression; - part.RebuildModel(); + if (!part.RebuildModel()) + throw new InvalidOperationException( + $"Не удалось пересчитать модель с выражением «{expression}» для '{name}' (недопустимое выражение или ссылка на несуществующую переменную?)."); return ReadValueFresh(name); // переменную перечитываем после перестроения (value пересчитан) }, ct); @@ -65,8 +68,10 @@ public sealed class VariableService var (part, vc) = RootVariables(); RequireVariable(vc, name); // понятная ошибка, если её нет if (!vc.RemoveVariable(name)) - throw new InvalidOperationException($"Не удалось удалить переменную '{name}'."); - part.RebuildModel(); + throw new InvalidOperationException( + $"Не удалось удалить переменную '{name}' (возможно, на неё ссылаются другие переменные — удалите зависимые сначала)."); + if (!part.RebuildModel()) + throw new InvalidOperationException($"Не удалось применить удаление переменной '{name}' (RebuildModel вернул FALSE)."); }, ct); /// Прочитать текущее (вычисленное) значение переменной. diff --git a/src/Kompas.Mcp.Host/Tools/VariableTools.cs b/src/Kompas.Mcp.Host/Tools/VariableTools.cs index 7f89f2e..56b433e 100644 --- a/src/Kompas.Mcp.Host/Tools/VariableTools.cs +++ b/src/Kompas.Mcp.Host/Tools/VariableTools.cs @@ -23,7 +23,7 @@ public sealed class VariableTools(KompasSession session, VariableService variabl } [McpServerTool(Name = "set_variable")] - [Description("Задать выражение существующей переменной: константа (например «30») или формула со ссылками на другие переменные (например «width*2+5»). Модель перестраивается, значение пересчитывается. Возвращает вычисленное значение.")] + [Description("Задать выражение существующей переменной: константа (например «30») или формула со ссылками на другие переменные (например «width*2+5»). Модель перестраивается, значение пересчитывается. Десятичный разделитель — точка. Примечание: изменит геометрию только в параметрической модели (где размеры эскизов связаны с именами переменных); для моделей с литеральными размерами значение пересчитается, но геометрия не изменится. Возвращает вычисленное значение.")] public async Task SetVariable( string name, [Description("Выражение: число или формула (width*2+5)")] string expression) diff --git a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs index bff3b3f..3e192dc 100644 --- a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs @@ -48,11 +48,21 @@ public sealed class VariableTests Assert.Equal(20, computed, 3); Assert.Equal(20, await _vars.GetVariableValueAsync("width"), 3); + // дубликат имени отвергается + await Assert.ThrowsAsync(() => _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(() => _vars.DeleteVariableAsync("width")); + // delete (в порядке зависимостей: сначала зависимая height, затем width) await _vars.DeleteVariableAsync("height"); await _vars.DeleteVariableAsync("width"); From 2d38cd33d8121f60fa6ab230f2a98703d288e5b7 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 11:28:33 +0300 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D0=B8=D0=BA=D0=B0=20(=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B8)=20=E2=80=94=20README/CLAUDE/ARCHITECTURE/OP?= =?UTF-8?q?EN=5FQUESTIONS/presentation,=2067=20=D0=B8=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=BE=D0=B2,=20116=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 9 +++++---- README.md | 7 ++++--- docs/ARCHITECTURE.md | 34 +++++++++++++++++++++++----------- docs/OPEN_QUESTIONS.md | 13 +++++++++++++ docs/presentation.html | 20 +++++++++++--------- 5 files changed, 56 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f915b7a..2c0d919 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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). diff --git a/README.md b/README.md index 8a540dc..0a682fe 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 41eabde..89c28bc 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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()` в `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** — как опция для удалённых клиентов. --- diff --git a/docs/OPEN_QUESTIONS.md b/docs/OPEN_QUESTIONS.md index 047d7a1..3cf6a45 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -160,3 +160,16 @@ stateful-сессии? Пока полагаемся на последовате **Ключевая тонкость (эмпирика):** `direction=false`=расширение (outward), `direction=true`=сужение — **обратно справке**. Маппинг в коде: `def.direction = !outward`. Тот же класс проблем с направлением, что у boss/cut (v2-1). Валидация угла вынесена в `SketchGeometry.RequireDraftAngle(0
- Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+draft (64 инструмента) + Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+draft+переменные (67 инструментов)

КОМПАС-3D MCP-сервер
управление CAD языком LLM

MCP-сервер, который превращает операции КОМПАС-3D — создание документов, эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом — @@ -261,8 +261,8 @@ впереди.

-
Общий прогрессv3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · offset-plane (пакет E start) · hole (API7) · draft (API5) · навык kompas-3d · 64 инструмента · 115 тестов
-
+
Общий прогресс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 тестов
+
@@ -297,7 +297,7 @@

Инструменты v3+ ГОТОВО

-

64 инструмента, отдаются по MCP-протоколу

+

67 инструментов, отдаются по MCP-протоколу

  • System: connect · status · set_visible
  • Documents: create · open · save · save_as · close · active
  • @@ -307,8 +307,9 @@
  • Inspection: describe_model · list_features · list_bodies · list_variables · describe_face · describe_edge · measure
  • Vision: model_snapshot 👁️ (fallback)
  • Query: get_part_info · get_bounding_box · list_faces · list_edges · list_components
  • +
  • Variables: create_variable · set_variable · delete_variable (пакет D)
  • Conversion: import_step · export_step
  • -
  • пакет E продолжение (ось, угловая плоскость)
  • +
  • пакет D продолжение (параметрические эскизы, API2D) · пакет E (ось, угловая плоскость)
@@ -319,7 +320,7 @@
  • Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText
  • Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция
  • -
  • Итого 115 тестов зелёных (69 unit + 46 integration)
  • +
  • Итого 116 тестов зелёных (69 unit + 47 integration)
  • Сборка kompas-mcp.exe, README с конфигом клиента
  • Навык kompas-3d (методика) + полигон usecases/
  • In-process тест MCP-клиента, релизный publish
  • @@ -344,7 +345,8 @@
    пакет B · готово ✓

    Формообразующие операции

    shell ✓ · rib ✓ · sweep ✓ · loft ✓ (по сечениям) · offset-plane ✓ (пакет E)

    пакет C · готово ✓

    Массивы и зеркало

    linear_pattern ✓ · circular_pattern ✓ · mirror_operation ✓ · mirror_body ✓ · hole ✓ (API7)

    draft · готово ✓

    Уклон

    draft ✓ (API5 ksInclineDefinition, o3d_incline=42) · 64 инструмента · 115 тестов

    -
    далее · в работе

    пакет E продолжение

    вспомогательная геометрия: ось, точка, угловая плоскость

    +
    пакет D start · готово ✓

    Переменные модели

    create_variable · set_variable · delete_variable (API5 VariableService) · list_variables исправлен · 67 инструментов · 116 тестов

    +
    далее · в работе

    пакет D продолжение + пакет E

    параметрические эскизы (API2D) · вспомогательная геометрия (ось, угловая плоскость)