From 5e6d3ec8ae0ef80c0929b8e5a4d644016751953d Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 13:24:58 +0300 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20=D1=81=D0=BF=D0=B5=D0=BA=20assembly?= =?UTF-8?q?=5Fadd=5Fmate=20=E2=80=94=20=D1=81=D0=BE=D0=BF=D1=80=D1=8F?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20(API7),=20=D1=81=D0=BF?= =?UTF-8?q?=D0=B0=D0=B9=D0=BA=20=D0=B7=D0=B5=D0=BB=D1=91=D0=BD=D1=8B=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Веха СБОРКИ, инкремент 2. Механизм подтверждён спайком: mates.Add(type) + BaseObject1/2 (грани через FindObjectsByPoint FirstLevel=false) + Update + RebuildModel. Coincidence (ширина 120→40), Distance(30) (→70). Свой enum MateType. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-27-assembly-add-mate-design.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md diff --git a/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md b/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md new file mode 100644 index 0000000..b6f4a70 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md @@ -0,0 +1,105 @@ +# Дизайн: сопряжение компонентов сборки (assembly_add_mate) через API7 + +**Дата:** 2026-05-27 +**Статус:** спайк зелёный (механизм подтверждён вживую); спроектировано; на ревью (Codex) + +## Цель + +Веха СБОРКИ, инкремент 2. После вставки компонентов (`assembly_add_component`, инкремент 1) — +**сопряжения**: наложить связь между гранями двух компонентов (совпадение / на расстоянии), +чтобы решатель сборки спозиционировал компоненты относительно друг друга. Это вторая +половина минимально полезного цикла сборки (вставить → связать). + +## Спайк: механизм подтверждён вживую (НЕ разучивать) + +Спайк (`AssemblyMateSpikeTests`, оба теста зелёные) подтвердил полный путь API7: + +``` +IPart7 top = (IPart7)doc3d.TopPart; // активная сборка +IMateConstraints3D mates = top.MateConstraints; // COM-свойство (не QI) +IMateConstraint3D mate = mates.Add(MateConstraintType); // напр. mc_Coincidence=0 / mc_Distance=5 +mate.BaseObject1 = (IModelObject)face1; // грань компонента 1 +mate.BaseObject2 = (IModelObject)face2; // грань компонента 2 +if (distance|angle) mate.ParamValue = value; // зазор (мм) / угол (°) +bool ok = mate.Update(); // наложить сопряжение +top.RebuildModel(true); // применить к геометрии +``` + +**Выбор граней компонентов:** `top.FindObjectsByPoint(x, y, z, FirstLevel=false)` — `FirstLevel=false` +заставляет спуститься В компоненты и вернуть их грань в **контексте сборки** (мировые координаты с +учётом placement компонента). Это именно тот `IModelObject`, который нужен решателю сопряжений. + +**Проверенные результаты (2 куба 20³, A в нуле x[0,20], B со сдвигом +100 x[100,120]):** +- **Совпадение** (`mc_Coincidence`) +X грани A (20,10,10) с −X гранью B (100,10,10): `Update=True`, + `Valid=True`, `Alignment=ksMCAlignmentClosest` (дефолт). Грани сошлись заподлицо → ширина сборки + по X **120 → 40** (кубы соприкасаются). Решатель подвинул A (к B), не B. +- **Расстояние** (`mc_Distance`, `ParamValue=30`) тех же граней: `Update=True`, `Valid=True`. Грани + встали в 30 мм → ширина сборки по X **120 → 70** (20+30+20). + +**Тонкости (эмпирика):** +- `Alignment` НЕ выставляем — дефолт работает (`ksMCAlignmentClosest` для совпадения, + `ksMCAlignmentUnknown` для расстояния). Параметр выравнивания — расширение позже (YAGNI). +- Решатель может двигать **любой** компонент (в т.ч. первый, «зафиксированный»). Поэтому результат + проверяем по **ширине габарита** (грани сблизились), а не по тому, какой компонент сдвинулся. +- `mates.Add` возвращает `IMateConstraint3D`; результат `Update()` проверяем (FALSE = не наложилось). + После — `RebuildModel(true)` (результат проверяем, как в инкременте 1). + +## Архитектура + +Метод в существующем `AssemblyService` (он уже про сборки). Свой enum `MateType` (паттерн +`BasePlane`/`CoordinateAxis`) маппится на КОМПАС `MateConstraintType` — COM-константы не текут в +Host/тесты. + +- `src/Kompas.Mcp.Core/Assemblies/MateType.cs` — enum `MateType` + static `Mates` + (`ToConstraintType`, `Parse(string)` англ./рус., `RequiresValue`). +- `AssemblyService.AddMateAsync(MateType, x1,y1,z1, x2,y2,z2, value, ct)` — реализация. +- Инструмент `assembly_add_mate` в `AssemblyTools` (тип сопряжения строкой → `Mates.Parse`). + +## MCP-инструмент + +| Инструмент | Параметры | Поведение | +|---|---|---| +| `assembly_add_mate` | `mateType: string`, `x1,y1,z1`, `x2,y2,z2`, `value=0` | Наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам (x1..z1) и (x2..z2). `mateType`: coincidence / distance / parallel / perpendicular / concentric / angle / tangency. Для distance — `value` зазор (мм), для angle — `value` угол (°). | + +Контракт: активный документ — сборка (`RequireActiveAssembly`, уже есть); обе точки должны попасть +на грани компонентов. **Гарантированно валидированы геометрией: coincidence, distance.** Остальные +типы маппятся и вызываемы через тот же путь Add+BaseObject+Update, но не покрыты регрессионным +тестом по габариту (требуют иной геометрии/проверки) — помечены в описании инструмента. + +## Валидация (чистые static, unit-тест) + +`Mates`: +- `Parse(string)` — англ./рус. → `MateType`; иначе `ArgumentException` со списком допустимых. +- `ToConstraintType(MateType)` — → `MateConstraintType`. +- `RequiresValue(MateType)` — true для Distance/Angle. + +В сервисе: `RequireFinitePosition` для обеих точек (уже есть в `AssemblyValidation`); если +`RequiresValue` — проверить `double.IsFinite(value)` (новый чистый валидатор +`AssemblyValidation.RequireFiniteValue`). + +## Реализация + +`AddMateCore`: валидация точек (и value, если нужен) → `RequireActiveAssembly` → +`FindFaceInAssembly` (FirstLevel=false) для обеих точек (понятная ошибка, если грань не найдена) → +`mates.Add(Mates.ToConstraintType(type))` → `BaseObject1/2` → `ParamValue` (если нужен) → +`Update()` (FALSE → откат сопряжения через `IFeature7.Delete` + ошибка) → `RebuildModel(true)` +(FALSE → ошибка). Транзитные RCW не освобождаем точечно (долг v2-2, как везде). + +## Тестирование + +### Unit (`MateTests` или в `AssemblyValidationTests`) +- `Mates.Parse` — англ. и рус. синонимы → верный `MateType`; бросает на мусоре. +- `Mates.ToConstraintType` — каждый `MateType` → ожидаемый `MateConstraintType`. +- `Mates.RequiresValue` — true для Distance/Angle, false для прочих. +- `AssemblyValidation.RequireFiniteValue` — бросает на NaN/Infinity. + +### Integration (`AssemblyMateTests`) +Паттерн: построить куб 20³ → сохранить → создать сборку → вставить 2 компонента (A в нуле, B +100) +→ сопряжение → проверить ширину габарита → `finally CloseAsync`. +1. **Совпадение** граней A(+X)/B(−X): ширина по X **120 → ~40** (соприкосновение). +2. **Расстояние 30**: ширина по X **120 → ~70** (зазор 30). +(Эти два сценария = промотированный спайк; спайк-файл удаляется.) + +## Дальнейшее (вне спека) +- Выравнивание (`Alignment`), типы concentric/angle/parallel с геометрической проверкой. +- Перемещение/фиксация/массив компонентов. From 8afad88241faad8b0d7424f30bb5c6c287ee2e08 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 13:38:48 +0300 Subject: [PATCH 2/5] =?UTF-8?q?test:=20per-test=20=D0=BE=D1=87=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Integrati?= =?UTF-8?q?onTestBase=20(CloseAllAsync)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Базовый класс IntegrationTestBase (IAsyncLifetime) закрывает все открытые документы КОМПАС после КАЖДОГО теста (DocumentService.CloseAllAsync) — чтобы не копились вкладки-пустышки и упавший Assert не оставлял открытый документ. Все интеграционные тест-классы переведены на него (: base(fx)); индивидуальные finally CloseAsync сохранены как первичная очистка. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Documents/DocumentService.cs | 20 ++++++++++++++++++ .../Integration/AssemblyTests.cs | 4 ++-- .../Integration/ConnectionTests.cs | 4 ++-- .../Integration/ConversionTests.cs | 4 ++-- .../Integration/DocumentTests.cs | 4 ++-- .../Integration/FaceEditTests.cs | 4 ++-- .../Integration/FeatureOpsTests.cs | 4 ++-- .../Kompas.Mcp.Tests/Integration/HoleTests.cs | 4 ++-- .../Integration/InspectionTests.cs | 4 ++-- .../Integration/IntegrationTestBase.cs | 21 +++++++++++++++++++ .../Integration/ModelingTests.cs | 4 ++-- .../Integration/QueryTests.cs | 4 ++-- .../Integration/SketchPrimitivesTests.cs | 4 ++-- .../Integration/SnapshotTests.cs | 4 ++-- .../Integration/SolidEditTests.cs | 4 ++-- .../Integration/ValidationTests.cs | 4 ++-- .../Integration/VariableTests.cs | 4 ++-- 17 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs diff --git a/src/Kompas.Mcp.Core/Documents/DocumentService.cs b/src/Kompas.Mcp.Core/Documents/DocumentService.cs index c3f1363..f409288 100644 --- a/src/Kompas.Mcp.Core/Documents/DocumentService.cs +++ b/src/Kompas.Mcp.Core/Documents/DocumentService.cs @@ -64,6 +64,26 @@ public sealed class DocumentService => _dispatcher.InvokeAsync(() => WithActive(d => d.Close(save ? DocumentCloseOptions.kdSaveChanges : DocumentCloseOptions.kdDoNotSaveChanges)), ct); + /// + /// Закрыть ВСЕ открытые документы без сохранения; возвращает число закрытых. Применяется для + /// очистки между тестами, чтобы не накапливались вкладки-пустышки. Закрываем активный документ + /// в цикле (после закрытия активным становится следующий) с защитой от зацикливания: если очередной + /// документ закрыть не удалось, прекращаем, чтобы не крутить впустую на одном и том же. + /// + public Task CloseAllAsync(CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + { + int closed = 0; + for (int guard = 0; guard < 256; guard++) + { + var doc = _session.Application.ActiveDocument; + if (doc is null) break; + try { doc.Close(DocumentCloseOptions.kdDoNotSaveChanges); closed++; } + finally { ComHelper.Release(doc); } + } + return closed; + }, ct); + private void WithActive(Action action) => WithActive(d => { action(d); return true; }); diff --git a/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs b/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs index 0c28a0b..945ce61 100644 --- a/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/AssemblyTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: вставка компонента в сборку (API7 IParts7.AddFromFile + Placement). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class AssemblyTests +public sealed class AssemblyTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly AssemblyService _assembly; private readonly QueryService _query; - public AssemblyTests(KompasFixture fx) + public AssemblyTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs b/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs index 2d0da18..99d91d0 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ConnectionTests.cs @@ -3,11 +3,11 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: подключение к КОМПАС. Требует установленный КОМПАС-3D. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ConnectionTests +public sealed class ConnectionTests : IntegrationTestBase { private readonly KompasFixture _fx; - public ConnectionTests(KompasFixture fx) => _fx = fx; + public ConnectionTests(KompasFixture fx) : base(fx) => _fx = fx; [Fact] public void Connects_to_kompas() diff --git a/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs b/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs index e49ffb2..a7db39b 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ConversionTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: импорт/экспорт STEP и обход компонентов сборки. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ConversionTests +public sealed class ConversionTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly ConversionService _conv; private readonly QueryService _query; - public ConversionTests(KompasFixture fx) + public ConversionTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs b/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs index dc803ab..0d0d688 100644 --- a/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs @@ -5,11 +5,11 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: жизненный цикл документов. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class DocumentTests +public sealed class DocumentTests : IntegrationTestBase { private readonly DocumentService _docs; - public DocumentTests(KompasFixture fx) + public DocumentTests(KompasFixture fx) : base(fx) => _docs = new DocumentService(fx.Session, fx.Dispatcher); [Fact] diff --git a/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs b/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs index 94cb163..87aa6b1 100644 --- a/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/FaceEditTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: прямое редактирование — перемещение грани (FaceMover). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class FaceEditTests +public sealed class FaceEditTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly FaceEditService _faceEdit; private readonly QueryService _query; - public FaceEditTests(KompasFixture fx) + public FaceEditTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs b/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs index 4d01397..805398f 100644 --- a/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: формообразующие операции пакетов B и C (массивы, зеркало). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class FeatureOpsTests +public sealed class FeatureOpsTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly QueryService _query; - public FeatureOpsTests(KompasFixture fx) + public FeatureOpsTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs b/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs index c2eac0c..3cedbff 100644 --- a/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/HoleTests.cs @@ -7,14 +7,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: операция «Отверстие» (API7 IHoles3D/IHole3D). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class HoleTests +public sealed class HoleTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly HoleService _holes; private readonly QueryService _query; - public HoleTests(KompasFixture fx) + public HoleTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs b/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs index 3eaa16f..30eabcf 100644 --- a/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/InspectionTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: структурное «зрение» — дерево, топология, drill-down, измерения. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class InspectionTests +public sealed class InspectionTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly ModelInspectionService _inspect; - public InspectionTests(KompasFixture fx) + public InspectionTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs b/tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs new file mode 100644 index 0000000..cc9e527 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/IntegrationTestBase.cs @@ -0,0 +1,21 @@ +using Kompas.Mcp.Core.Documents; + +namespace Kompas.Mcp.Tests.Integration; + +/// +/// Базовый класс интеграционных тестов: после КАЖДОГО теста закрывает все открытые документы +/// КОМПАС без сохранения, чтобы не копились вкладки-пустышки (и чтобы упавший Assert не оставлял +/// открытый документ). xUnit создаёт новый экземпляр тест-класса на каждый тест-метод, поэтому +/// вызывается per-test — это и есть точка очистки. +/// +public abstract class IntegrationTestBase : IAsyncLifetime +{ + private readonly DocumentService _cleanupDocs; + + protected IntegrationTestBase(KompasFixture fx) + => _cleanupDocs = new DocumentService(fx.Session, fx.Dispatcher); + + public Task InitializeAsync() => Task.CompletedTask; + + public Task DisposeAsync() => _cleanupDocs.CloseAllAsync(); +} diff --git a/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs b/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs index 4f5d3b4..914ba0c 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ModelingTests.cs @@ -8,14 +8,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: построение 3D (эскиз → выдавливание) и снимок. [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ModelingTests +public sealed class ModelingTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly SnapshotService _snap; private readonly QueryService _query; - public ModelingTests(KompasFixture fx) + public ModelingTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs b/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs index b390c1c..0ad095c 100644 --- a/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/QueryTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: массово-центровочные характеристики (get_part_info). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class QueryTests +public sealed class QueryTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly QueryService _query; - public QueryTests(KompasFixture fx) + public QueryTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs b/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs index e108e53..cb85ac0 100644 --- a/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: новые примитивы эскиза (профиль → выдавливание → объём). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class SketchPrimitivesTests +public sealed class SketchPrimitivesTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly QueryService _query; - public SketchPrimitivesTests(KompasFixture fx) + public SketchPrimitivesTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs b/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs index 1b8d00a..69d8b73 100644 --- a/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/SnapshotTests.cs @@ -6,12 +6,12 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: снимок модели в растр («зрение агента»). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class SnapshotTests +public sealed class SnapshotTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly SnapshotService _snap; - public SnapshotTests(KompasFixture fx) + public SnapshotTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _snap = new SnapshotService(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs b/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs index a66e46f..4f0d374 100644 --- a/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/SolidEditTests.cs @@ -9,7 +9,7 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: рассечение тела, перемещение тела, объединение (cut→spread→union). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class SolidEditTests +public sealed class SolidEditTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; @@ -18,7 +18,7 @@ public sealed class SolidEditTests private readonly QueryService _query; private readonly ValidationService _validation; - public SolidEditTests(KompasFixture fx) + public SolidEditTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs b/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs index 39cb0f0..c15f8b6 100644 --- a/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/ValidationTests.cs @@ -7,13 +7,13 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: валидация построения (ошибки/перестроение операций). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class ValidationTests +public sealed class ValidationTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly ValidationService _validation; - public ValidationTests(KompasFixture fx) + public ValidationTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); diff --git a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs index 3e192dc..c13228a 100644 --- a/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/VariableTests.cs @@ -7,14 +7,14 @@ namespace Kompas.Mcp.Tests.Integration; /// Интеграция: переменные модели (параметрика, API5 ksVariable). [Trait("Category", "Integration")] [Collection(KompasCollection.Name)] -public sealed class VariableTests +public sealed class VariableTests : IntegrationTestBase { private readonly DocumentService _docs; private readonly PartModeler _modeler; private readonly VariableService _vars; private readonly ModelInspectionService _inspect; - public VariableTests(KompasFixture fx) + public VariableTests(KompasFixture fx) : base(fx) { _docs = new DocumentService(fx.Session, fx.Dispatcher); _modeler = new PartModeler(fx.Session, fx.Dispatcher); From 8282716773e366c9c6123053eaed52c1d3aeb2b9 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 13:39:04 +0300 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20assembly=5Fadd=5Fmate=20=E2=80=94?= =?UTF-8?q?=20=D1=81=D0=BE=D0=BF=D1=80=D1=8F=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20(API7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Веха СБОРКИ, инкремент 2. AssemblyService.AddMateAsync (API7): top.MateConstraints → Add(MateConstraintType) → BaseObject1/2 (грани через FindObjectsByPoint FirstLevel=false) → ParamValue → Update → RebuildModel. Свой enum MateType (Coincidence/Distance) → MateConstraintType. MCP-инструмент assembly_add_mate. Подтверждено спайком и тестами: coincidence сводит грани заподлицо (ширина 120→40), distance(30) держит зазор 30 (→70); Update=True Valid=True. Решатель может двигать любой компонент → проверка по ширине габарита. Ревью Codex учтено: scope урезан до coincidence/distance (только геометрически проверенные); валидация value по типу (RequireMateValue); полный откат при любой ошибке после Add (mate.Owner.Delete); возврат AddedMate{Type,Valid}; усиленные ассерты (Valid, размеры компонентов неизменны). Точечный release RCW отклонён (конвенция v2-2). Прочие типы (parallel/concentric/angle/…) — будущие инкременты. Тесты: +14 unit (Mates.Parse/RequiresValue, RequireMateValue), +2 integration (AssemblyMateTests). Итого 97 unit + 57 integration = 154 зелёных. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-27-assembly-add-mate-design.md | 22 ++++- src/Kompas.Mcp.Core/Assemblies/AddedMate.cs | 11 +++ .../Assemblies/AssemblyService.cs | 72 ++++++++++++++ .../Assemblies/AssemblyValidation.cs | 16 ++- src/Kompas.Mcp.Core/Assemblies/MateType.cs | 44 +++++++++ src/Kompas.Mcp.Host/Tools/AssemblyTools.cs | 22 +++++ .../AssemblyValidationTests.cs | 42 ++++++++ .../Integration/AssemblyMateTests.cs | 98 +++++++++++++++++++ 8 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 src/Kompas.Mcp.Core/Assemblies/AddedMate.cs create mode 100644 src/Kompas.Mcp.Core/Assemblies/MateType.cs create mode 100644 tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs diff --git a/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md b/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md index b6f4a70..b0b8ea3 100644 --- a/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md +++ b/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md @@ -1,7 +1,27 @@ # Дизайн: сопряжение компонентов сборки (assembly_add_mate) через API7 **Дата:** 2026-05-27 -**Статус:** спайк зелёный (механизм подтверждён вживую); спроектировано; на ревью (Codex) +**Статус:** спайк зелёный; ревью Codex учтено (8 замечаний); реализуется + +## Решения по ревью Codex (важные правки к дизайну ниже) + +- **Scope урезан до `coincidence` + `distance`** (#6, #5): только эти два типа геометрически + подтверждены спайком → только они в публичном `MateType`/`Parse`. Остальные (parallel/ + perpendicular/concentric/angle/tangency) и `Alignment`-параметр — будущие инкременты с тестами. +- **Валидация value по типу** (#1): `Mates.RequiresValue` → distance требует `value` конечный и + `≥ 0`; для типов без value параметр игнорируется (дефолт 0). Валидатор + `AssemblyValidation.RequireMateValue(requiresValue, value)`. +- **Полный откат** (#3): весь участок после `mates.Add` — в try/catch; при ЛЮБОЙ ошибке (BaseObject, + ParamValue, Update=FALSE, RebuildModel=FALSE) сопряжение удаляется через `mate.Owner?.Delete()` + (`IFeature7`, как у компонента в инкременте 1). +- **Возврат результата** (#8b): `AddedMate { Type: string, Valid: bool }` — для отчёта инструмента и + ассертов теста (`Valid`). +- **Усиленный тест** (#2): явно `bbBefore ≈ 120`; после mate — `Valid==true`, размеры каждого + компонента (`SizeX≈20`) неизменны, ширина сборки изменилась. +- **RCW** (#7): точечный release НЕ вводим (конвенция v2-2, как в инкременте 1/HoleService); все + COM-вызовы — внутри `dispatcher.InvokeAsync` (уже так). Долг зафиксирован в OPEN_QUESTIONS v2-2. +- **Выбор грани** (#8a): паттерн «первый IFace» (как в HoleService/move_face); в описании + инструмента — «точка в СЕРЕДИНЕ грани» (на ребре/углу хит неоднозначен). ## Цель diff --git a/src/Kompas.Mcp.Core/Assemblies/AddedMate.cs b/src/Kompas.Mcp.Core/Assemblies/AddedMate.cs new file mode 100644 index 0000000..e9e35b5 --- /dev/null +++ b/src/Kompas.Mcp.Core/Assemblies/AddedMate.cs @@ -0,0 +1,11 @@ +namespace Kompas.Mcp.Core.Assemblies; + +/// Результат наложения сопряжения между компонентами сборки. +public sealed record AddedMate +{ + /// Тип наложенного сопряжения (как был запрошен). + public required MateType Type { get; init; } + + /// true — сопряжение успешно построено и не вырождено после перестроения. + public required bool Valid { get; init; } +} diff --git a/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs b/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs index 5393bcb..197855b 100644 --- a/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs +++ b/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs @@ -1,6 +1,7 @@ using System.Runtime.Versioning; using Kompas.Mcp.Core.Threading; using Kompas6Constants; +using Kompas6Constants3D; using KompasAPI7; namespace Kompas.Mcp.Core.Assemblies; @@ -79,6 +80,77 @@ public sealed class AssemblyService } } + /// + /// Наложить сопряжение между гранями двух компонентов активной сборки, + /// выбранными по мировым точкам (,,) + /// и (,,). Для расстояния + /// — зазор (мм). Решатель спозиционирует компоненты; результат — статус + /// построенного сопряжения. + /// + public Task AddMateAsync(MateType type, double x1, double y1, double z1, + double x2, double y2, double z2, double value, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => AddMateCore(type, x1, y1, z1, x2, y2, z2, value), ct); + + private AddedMate AddMateCore(MateType type, double x1, double y1, double z1, + double x2, double y2, double z2, double value) + { + AssemblyValidation.RequireFinitePosition(x1, y1, z1); + AssemblyValidation.RequireFinitePosition(x2, y2, z2); + AssemblyValidation.RequireMateValue(Mates.RequiresValue(type), value); + + var top = RequireActiveAssembly(); + var face1 = FindFaceInAssembly(top, x1, y1, z1) + ?? throw new InvalidOperationException( + $"В точке ({x1},{y1},{z1}) не найдена грань компонента. Укажите точку в середине грани (model_snapshot)."); + var face2 = FindFaceInAssembly(top, x2, y2, z2) + ?? throw new InvalidOperationException( + $"В точке ({x2},{y2},{z2}) не найдена грань компонента. Укажите точку в середине грани (model_snapshot)."); + + var mates = top.MateConstraints + ?? throw new InvalidOperationException("Не удалось получить коллекцию сопряжений сборки (MateConstraints)."); + var mate = mates.Add(Mates.ToConstraintType(type)) + ?? throw new InvalidOperationException("MateConstraints.Add вернул null."); + + try + { + mate.BaseObject1 = (IModelObject)face1; + mate.BaseObject2 = (IModelObject)face2; + if (Mates.RequiresValue(type)) + mate.ParamValue = value; + + if (!mate.Update()) + throw new InvalidOperationException( + "Update сопряжения вернул FALSE — связь не наложена (несовместимые грани/значение?)."); + if (!top.RebuildModel(true)) + throw new InvalidOperationException("RebuildModel вернул FALSE — сборка не перестроена после сопряжения."); + + return new AddedMate { Type = type, Valid = mate.Valid }; + } + catch + { + // Полный откат: при ЛЮБОЙ ошибке после Add удаляем сопряжение, чтобы не оставить «сироту». + try { mate.Owner?.Delete(); top.RebuildModel(true); } catch { /* лучшее усилие */ } + throw; + } + } + + private static IFace? FindFaceInAssembly(IPart7 top, double x, double y, double z) + { + // FirstLevel=false — спускаемся в компоненты, чтобы получить их грани в контексте сборки. + var hit = top.FindObjectsByPoint(x, y, z, false); + switch (hit) + { + case IFace f: return f; + case object[] arr: + foreach (var o in arr) if (o is IFace face) return face; + return null; + case System.Collections.IEnumerable en: + foreach (var o in en) if (o is IFace face) return face; + return null; + default: return null; + } + } + /// /// Откатить вставку: удалить компоненты, появившиеся сверх (обычно /// один — только что вставленный). Best-effort, но с верификацией: компонент удаляется через diff --git a/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs b/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs index 7bbafc3..68c44c7 100644 --- a/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs +++ b/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs @@ -29,10 +29,22 @@ public static class AssemblyValidation return Path.GetFullPath(trimmed); } - /// Проверить, что координаты вставки конечны (отсекает NaN/Infinity). + /// Проверить, что координаты конечны (отсекает NaN/Infinity). public static void RequireFinitePosition(double x, double y, double z) { if (!double.IsFinite(x) || !double.IsFinite(y) || !double.IsFinite(z)) - throw new ArgumentException("Координаты вставки компонента должны быть конечными числами (не NaN/Infinity)."); + throw new ArgumentException("Координаты должны быть конечными числами (не NaN/Infinity)."); + } + + /// + /// Проверить числовое значение сопряжения. Если значение требуется (, + /// напр. расстояние) — оно должно быть конечным и неотрицательным. + /// + public static void RequireMateValue(bool requiresValue, double value) + { + if (!requiresValue) return; // для типов без параметра значение игнорируется + if (!double.IsFinite(value) || value < 0) + throw new ArgumentOutOfRangeException(nameof(value), + "Значение сопряжения (расстояние) должно быть конечным и неотрицательным."); } } diff --git a/src/Kompas.Mcp.Core/Assemblies/MateType.cs b/src/Kompas.Mcp.Core/Assemblies/MateType.cs new file mode 100644 index 0000000..7dbe02e --- /dev/null +++ b/src/Kompas.Mcp.Core/Assemblies/MateType.cs @@ -0,0 +1,44 @@ +using Kompas6Constants3D; + +namespace Kompas.Mcp.Core.Assemblies; + +/// +/// Дружелюбный тип сопряжения (обёртка над ). Пока поддержаны только +/// геометрически проверенные типы — совпадение и расстояние; остальные (параллельность, соосность, +/// угол и т.д.) добавятся в следующих инкрементах вместе с тестами. +/// +public enum MateType +{ + /// Совпадение (грани лежат на одной плоскости, заподлицо). + Coincidence, + /// На расстоянии (постоянный зазор между гранями, мм). + Distance, +} + +/// Маппинг дружелюбного типа сопряжения ↔ перечисление API КОМПАС и разбор строки. +public static class Mates +{ + /// Сопряжения, которым нужно числовое значение (ParamValue): расстояние. + public static bool RequiresValue(MateType type) => type is MateType.Distance; + + public static MateConstraintType ToConstraintType(MateType type) => type switch + { + MateType.Coincidence => MateConstraintType.mc_Coincidence, + MateType.Distance => MateConstraintType.mc_Distance, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Неизвестный тип сопряжения."), + }; + + /// Разобрать строку типа сопряжения (англ./рус.) в . + public static MateType Parse(string value) + { + ArgumentNullException.ThrowIfNull(value); + return value.Trim().ToLowerInvariant() switch + { + "coincidence" or "coincident" or "совпадение" => MateType.Coincidence, + "distance" or "расстояние" => MateType.Distance, + _ => throw new ArgumentException( + $"Неизвестный/неподдерживаемый тип сопряжения '{value}'. Сейчас поддержаны: " + + "coincidence (совпадение), distance (расстояние).", nameof(value)), + }; + } +} diff --git a/src/Kompas.Mcp.Host/Tools/AssemblyTools.cs b/src/Kompas.Mcp.Host/Tools/AssemblyTools.cs index d192e99..c6f7477 100644 --- a/src/Kompas.Mcp.Host/Tools/AssemblyTools.cs +++ b/src/Kompas.Mcp.Host/Tools/AssemblyTools.cs @@ -24,4 +24,26 @@ public sealed class AssemblyTools(KompasSession session, AssemblyService assembl var c = await assembly.AddComponentAsync(filePath, x, y, z); return $"Компонент '{c.Name}' вставлен в сборку, индекс {c.Index}, позиция ({x}, {y}, {z})."; } + + [McpServerTool(Name = "assembly_add_mate")] + [Description("Наложить сопряжение между гранями ДВУХ компонентов активной сборки, выбранными по " + + "мировым точкам (x1,y1,z1) и (x2,y2,z2) — точки в СЕРЕДИНЕ граней (мм). mateType: " + + "'coincidence' (совпадение, грани заподлицо) или 'distance' (на расстоянии value мм между гранями). " + + "Решатель спозиционирует компоненты. Сделайте model_snapshot/describe_face, прикиньте точки на гранях.")] + public async Task AddMate( + [Description("Тип сопряжения: coincidence | distance")] string mateType, + [Description("X точки на грани компонента 1, мм")] double x1, + [Description("Y точки на грани компонента 1, мм")] double y1, + [Description("Z точки на грани компонента 1, мм")] double z1, + [Description("X точки на грани компонента 2, мм")] double x2, + [Description("Y точки на грани компонента 2, мм")] double y2, + [Description("Z точки на грани компонента 2, мм")] double z2, + [Description("Зазор для distance, мм (для coincidence не нужен)")] double value = 0) + { + await session.ConnectAsync(); + var type = Mates.Parse(mateType); + var m = await assembly.AddMateAsync(type, x1, y1, z1, x2, y2, z2, value); + var valueText = Mates.RequiresValue(type) ? $", значение {value} мм" : ""; + return $"Сопряжение '{type}'{valueText} наложено (valid={m.Valid})."; + } } diff --git a/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs b/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs index 4c56243..b646961 100644 --- a/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs +++ b/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs @@ -41,4 +41,46 @@ public sealed class AssemblyValidationTests [Fact] public void RequireFinitePosition_accepts_finite() => Assert.Null(Record.Exception(() => AssemblyValidation.RequireFinitePosition(50, -10, 0))); + + // ── Сопряжения (Mates) ─────────────────────────────────────────────── + + [Theory] + [InlineData("coincidence", MateType.Coincidence)] + [InlineData("совпадение", MateType.Coincidence)] + [InlineData("DISTANCE", MateType.Distance)] + [InlineData(" расстояние ", MateType.Distance)] + public void Mates_Parse_maps_known(string text, MateType expected) + => Assert.Equal(expected, Mates.Parse(text)); + + [Theory] + [InlineData("parallel")] // пока не поддержан + [InlineData("сопряжение")] + [InlineData("")] + public void Mates_Parse_throws_on_unknown(string text) + => Assert.Throws(() => Mates.Parse(text)); + + // Корректность ToConstraintType проверяется интеграционно (coincidence даёт прилегание, + // distance — зазор), а не unit-ассертом: КОМПАС-enum встроен (EmbedInteropTypes) и не виден + // тестовому проекту через границу сборки. + + [Theory] + [InlineData(MateType.Distance, true)] + [InlineData(MateType.Coincidence, false)] + public void Mates_RequiresValue(MateType type, bool expected) + => Assert.Equal(expected, Mates.RequiresValue(type)); + + [Fact] + public void RequireMateValue_ignores_value_when_not_required() + => Assert.Null(Record.Exception(() => AssemblyValidation.RequireMateValue(false, double.NaN))); + + [Theory] + [InlineData(double.NaN)] + [InlineData(double.PositiveInfinity)] + [InlineData(-1)] + public void RequireMateValue_throws_on_bad_required_value(double value) + => Assert.Throws(() => AssemblyValidation.RequireMateValue(true, value)); + + [Fact] + public void RequireMateValue_accepts_nonnegative_required_value() + => Assert.Null(Record.Exception(() => AssemblyValidation.RequireMateValue(true, 30))); } diff --git a/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs b/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs new file mode 100644 index 0000000..d80a89f --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs @@ -0,0 +1,98 @@ +using Kompas.Mcp.Core.Assemblies; +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Modeling; +using Kompas.Mcp.Core.Query; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: сопряжения компонентов сборки (API7 IMateConstraints3D). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class AssemblyMateTests : IntegrationTestBase +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly AssemblyService _assembly; + private readonly QueryService _query; + + public AssemblyMateTests(KompasFixture fx) : base(fx) + { + _docs = new DocumentService(fx.Session, fx.Dispatcher); + _modeler = new PartModeler(fx.Session, fx.Dispatcher); + _assembly = new AssemblyService(fx.Session, fx.Dispatcher); + _query = new QueryService(fx.Session, fx.Dispatcher); + } + + private async Task BuildAndSaveCubeAsync(double side) + { + var path = TestPaths.NewFile(".m3d"); + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + var s = await _modeler.OpenSketchAsync(BasePlane.XOY); + await _modeler.AddRectangleAsync(s, 0, 0, side, side); + await _modeler.CloseSketchAsync(s); + await _modeler.ExtrudeAsync(s, depth: side); + await _modeler.RebuildAsync(); + await _docs.SaveAsAsync(path); + } + finally { await _docs.CloseAsync(save: false); } + return path; + } + + /// Создать сборку из двух кубов 20³: A в нуле (world x[0,20]), B со сдвигом +100 (x[100,120]). + private async Task SetupTwoCubesAsync() + { + var cube = await BuildAndSaveCubeAsync(20); + await _docs.CreateAsync(KompasDocumentType.Assembly); + await _assembly.AddComponentAsync(cube, 0, 0, 0); + await _assembly.AddComponentAsync(cube, 100, 0, 0); + return cube; + } + + [Fact] + public async Task Coincidence_mate_brings_faces_flush() + { + await SetupTwoCubesAsync(); + try + { + var bbBefore = await _query.GetBoundingBoxAsync(); + Assert.InRange(bbBefore.MaxX - bbBefore.MinX, 118, 122); // 2 куба врозь ≈ 120 + + // Совпадение +X грани A (20,10,10) с −X гранью B (100,10,10). + var m = await _assembly.AddMateAsync(MateType.Coincidence, 20, 10, 10, 100, 10, 10, value: 0); + Assert.True(m.Valid, "Сопряжение должно быть валидным."); + + var bbAfter = await _query.GetBoundingBoxAsync(); + Assert.InRange(bbAfter.MaxX - bbAfter.MinX, 38, 42); // кубы соприкоснулись ≈ 40 + + // Размеры самих компонентов не изменились (двигаются, не деформируются). + var comps = await _query.ListComponentsAsync(); + Assert.Equal(2, comps.Count); + Assert.All(comps, c => Assert.InRange(c.SizeX, 19, 21)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Distance_mate_holds_faces_apart() + { + await SetupTwoCubesAsync(); + try + { + var bbBefore = await _query.GetBoundingBoxAsync(); + Assert.InRange(bbBefore.MaxX - bbBefore.MinX, 118, 122); + + // Расстояние 30 между +X гранью A и −X гранью B → ширина 20+30+20 = 70. + var m = await _assembly.AddMateAsync(MateType.Distance, 20, 10, 10, 100, 10, 10, value: 30); + Assert.True(m.Valid, "Сопряжение должно быть валидным."); + + var bbAfter = await _query.GetBoundingBoxAsync(); + Assert.InRange(bbAfter.MaxX - bbAfter.MinX, 66, 74); // ≈ 70 + + var comps = await _query.ListComponentsAsync(); + Assert.All(comps, c => Assert.InRange(c.SizeX, 19, 21)); + } + finally { await _docs.CloseAsync(save: false); } + } +} From 3fe06f61dcb3a649ce6f546913b0e685b81f9a4d Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 13:45:35 +0300 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20assembly=5Fadd=5Fmate=20=E2=80=94?= =?UTF-8?q?=20=D1=83=D0=BF=D1=80=D0=BE=D1=87=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20Codex=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1: Valid==false → ошибка (откат), а не «успех с valid=false»; косвенно отсекает грани одного компонента (вырожденный мат). #6/#7: distance value строго > 0 (нулевой зазор = coincidence) + unit-кейс value=0 бросает. #8: негативный интеграционный тест — точка в пустоте → грань не найдена → ошибка. #2/#3/#4 оставлены по обоснованию (best-effort откол, Add??throw, первый IFace — консистентно с HoleService). #9 точечный release RCW отклонён (конвенция v2-2). Тесты: 98 unit + 58 integration = 156 зелёных. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../specs/2026-05-27-assembly-add-mate-design.md | 15 ++++++++++++++- src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs | 6 +++++- .../Assemblies/AssemblyValidation.cs | 8 +++++--- tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs | 3 ++- .../Integration/AssemblyMateTests.cs | 13 +++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md b/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md index b0b8ea3..1646c7a 100644 --- a/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md +++ b/docs/superpowers/specs/2026-05-27-assembly-add-mate-design.md @@ -1,7 +1,20 @@ # Дизайн: сопряжение компонентов сборки (assembly_add_mate) через API7 **Дата:** 2026-05-27 -**Статус:** спайк зелёный; ревью Codex учтено (8 замечаний); реализуется +**Статус:** реализовано и проверено (2 интеграционных + 14 unit, всего 154); ревью Codex спека (8) и реализации (9) учтено + +## Правки по ревью реализации Codex (9 замечаний) + +- **#1 `Valid==false` → ошибка** (а не «успех с valid=false»): после Update+Rebuild проверяем + `mate.Valid`, иначе бросаем (срабатывает откат). `AddedMate.Valid` теперь всегда true при успехе. + Это же косвенно ловит грани одного компонента (#5) — мат выродится. +- **#6/#7 distance `value` > 0** (не ≥0): нулевой зазор вырождается в coincidence → + `RequireMateValue` требует строго `> 0`; unit-кейс `value=0` бросает. +- **#8 негативный интеграционный тест**: точка в пустоте → грань не найдена → ошибка. +- **#2 откат best-effort** оставлен намеренно (исходное исключение важнее ошибки отката; паттерн + `HoleService`). **#3** `mates.Add ?? throw` до try корректно (null = ничего не добавлено). + **#4** «первый IFace» оставлен (консистентно с HoleService/move_face). **#9** точечный release + RCW отклонён (конвенция v2-2). ## Решения по ревью Codex (важные правки к дизайну ниже) diff --git a/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs b/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs index 197855b..278e9cb 100644 --- a/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs +++ b/src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs @@ -123,8 +123,12 @@ public sealed class AssemblyService "Update сопряжения вернул FALSE — связь не наложена (несовместимые грани/значение?)."); if (!top.RebuildModel(true)) throw new InvalidOperationException("RebuildModel вернул FALSE — сборка не перестроена после сопряжения."); + if (!mate.Valid) + throw new InvalidOperationException( + "Сопряжение построено, но вырождено (Valid=false) — вероятно, грани несовместимы " + + "или принадлежат одному компоненту. Проверьте выбор граней."); - return new AddedMate { Type = type, Valid = mate.Valid }; + return new AddedMate { Type = type, Valid = true }; } catch { diff --git a/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs b/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs index 68c44c7..c6bfd8c 100644 --- a/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs +++ b/src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs @@ -38,13 +38,15 @@ public static class AssemblyValidation /// /// Проверить числовое значение сопряжения. Если значение требуется (, - /// напр. расстояние) — оно должно быть конечным и неотрицательным. + /// напр. расстояние) — оно должно быть конечным и строго положительным (зазор 0 вырождается в + /// совпадение — для него есть отдельный тип). /// public static void RequireMateValue(bool requiresValue, double value) { if (!requiresValue) return; // для типов без параметра значение игнорируется - if (!double.IsFinite(value) || value < 0) + if (!double.IsFinite(value) || value <= 0) throw new ArgumentOutOfRangeException(nameof(value), - "Значение сопряжения (расстояние) должно быть конечным и неотрицательным."); + "Значение сопряжения (расстояние) должно быть конечным и > 0 " + + "(для нулевого зазора используйте тип coincidence)."); } } diff --git a/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs b/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs index b646961..2e09238 100644 --- a/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs +++ b/tests/Kompas.Mcp.Tests/AssemblyValidationTests.cs @@ -77,10 +77,11 @@ public sealed class AssemblyValidationTests [InlineData(double.NaN)] [InlineData(double.PositiveInfinity)] [InlineData(-1)] + [InlineData(0)] // нулевой зазор вырождается в coincidence → ошибка public void RequireMateValue_throws_on_bad_required_value(double value) => Assert.Throws(() => AssemblyValidation.RequireMateValue(true, value)); [Fact] - public void RequireMateValue_accepts_nonnegative_required_value() + public void RequireMateValue_accepts_positive_required_value() => Assert.Null(Record.Exception(() => AssemblyValidation.RequireMateValue(true, 30))); } diff --git a/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs b/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs index d80a89f..174b57f 100644 --- a/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/AssemblyMateTests.cs @@ -95,4 +95,17 @@ public sealed class AssemblyMateTests : IntegrationTestBase } finally { await _docs.CloseAsync(save: false); } } + + [Fact] + public async Task Mate_with_point_off_any_face_throws() + { + await SetupTwoCubesAsync(); + try + { + // Вторая точка — в пустоте (далеко от обоих кубов): грань не найдена → понятная ошибка. + await Assert.ThrowsAsync( + () => _assembly.AddMateAsync(MateType.Coincidence, 20, 10, 10, 1000, 1000, 1000, value: 0)); + } + finally { await _docs.CloseAsync(save: false); } + } } From 1493eaa776bfb977bfc295657e79adff9cde6bcb Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 13:52:35 +0300 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20assembly=5Fadd=5Fmate=20=E2=80=94?= =?UTF-8?q?=20=D1=81=D0=BE=D0=BF=D1=80=D1=8F=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D1=8B=20(72=20=D0=B8?= =?UTF-8?q?=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0,?= =?UTF-8?q?=20156=20=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 README/CLAUDE/ARCHITECTURE/OPEN_QUESTIONS/presentation.html: счётчики 71→72, 138→156; группа Assembly +assembly_add_mate; факты AddMateAsync (IMateConstraints3D, FindObjectsByPoint FirstLevel=false, MateType, Valid-проверка); упомянут IntegrationTestBase (per-test очистка). Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 8 ++++---- README.md | 6 +++--- docs/ARCHITECTURE.md | 11 ++++++----- docs/OPEN_QUESTIONS.md | 5 ++++- docs/presentation.html | 17 +++++++++-------- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cb66a24..e58f687 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**. -**71 MCP tools, 138 tests green (83 unit + 55 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`). **`hole_counterbore`** — отверстие с цековкой (`ksHTCounterbore`, `ISpotfacingHoleParameters`). **`hole_countersink`** — отверстие с зенковкой (`ksHTCountersinking`, `ICountersinkHoleParameters`). **`hole_conic`** — коническое отверстие (`ksHTConic`, `IConicHoleParameters`: `ConicType=ksCNAngle`, `ConicAngle`); завершает набор типов отверстий. `HoleCore` рефакторен: принимает `ksHoleTypeEnum holeType` + `Action configure` (настройка `HoleParameters` после установки `HoleType`, т.к. параметрический подынтерфейс недоступен до фиксации типа). Базовый `hole` = `ksHTBase` + `configure=null`. **`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) — не реализована. **Assembly class started:** **`assembly_add_component`** (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`) — первый инструмент класса СБОРКИ: вставка компонента (.m3d/.a3d) из файла в активную сборку с заданием положения origin (мм, мировая СК сборки); возвращает индекс и имя вставленного компонента. +**72 MCP tools, 156 tests green (98 unit + 58 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`). **`hole_counterbore`** — отверстие с цековкой (`ksHTCounterbore`, `ISpotfacingHoleParameters`). **`hole_countersink`** — отверстие с зенковкой (`ksHTCountersinking`, `ICountersinkHoleParameters`). **`hole_conic`** — коническое отверстие (`ksHTConic`, `IConicHoleParameters`: `ConicType=ksCNAngle`, `ConicAngle`); завершает набор типов отверстий. `HoleCore` рефакторен: принимает `ksHoleTypeEnum holeType` + `Action configure` (настройка `HoleParameters` после установки `HoleType`, т.к. параметрический подынтерфейс недоступен до фиксации типа). Базовый `hole` = `ksHTBase` + `configure=null`. **`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) — не реализована. **Assembly class:** **`assembly_add_component`** (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`) — вставка компонента (.m3d/.a3d) из файла в активную сборку с заданием положения origin (мм, мировая СК сборки); возвращает индекс и имя вставленного компонента. **`assembly_add_mate`** — наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам; `mateType`: `coincidence` (грани заподлицо) | `distance` (зазор `value` мм, `value>0`); решатель позиционирует компоненты; возвращает статус (`valid`). Подтверждено вживую: `coincidence` (ширина сборки 120→40 мм), `distance(30)` (→70 мм). See [`README.md`](README.md) and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); design decisions/caveats in [`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). @@ -49,11 +49,11 @@ Key implementation facts (don't relearn): - **Face/edge selection**: `ksPart.EntityCollection(o3d_face|o3d_edge)` → `SelectByPoint(x,y,z)` (world mm) filters to objects through the point, then `GetByIndex(0)`; or by stable index from `list_faces`/`list_edges`. `ksFaceDefinition.IsPlanar/IsCylinder/...` + `GetArea(ST_MIX_MM)` classify faces. `ksEdgeDefinition.IsLineSeg/IsCircle/IsArc/...` + `GetLength()` classify/measure edges. Sketch axis = line with system style **3** (осевая). Fillet/chamfer by edge index via `fillet_edge_index`/`chamfer_edge_index` (more reliable than by-point). - **МЦХ / 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/`. +- Logs go to **stderr** (stdout is the MCP channel). Integration tests reuse one КОМПАС via `KompasFixture`; artifacts land in gitignored `.scratch/`. All integration test classes inherit `IntegrationTestBase` (`IAsyncLifetime`) which calls `DocumentService.CloseAllAsync` after each test to prevent tab accumulation in КОМПАС. - **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`, `hole_counterbore`, `hole_countersink`, `hole_conic` (all API7 via `HoleService`; `hole_conic` uses `ksHTConic`/`IConicHoleParameters`), `draft` (API5 `ksInclineDefinition`), `create_variable`/`set_variable`/`delete_variable` (package D start), `assembly_add_component` (API7, `AssemblyService`) are done.** Not yet done: parametric sketches — **investigated, unavailable via COM API** (`ksCDimWithVariable` cannot be constructed from external automation; see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`); 2D drawing; assembly mates/constraints; 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`, `hole_counterbore`, `hole_countersink`, `hole_conic` (all API7 via `HoleService`; `hole_conic` uses `ksHTConic`/`IConicHoleParameters`), `draft` (API5 `ksInclineDefinition`), `create_variable`/`set_variable`/`delete_variable` (package D start), `assembly_add_component`, `assembly_add_mate` (both API7, `AssemblyService`; mate types `coincidence`/`distance` verified) are done.** Not yet done: parametric sketches — **investigated, unavailable via COM API** (`ksCDimWithVariable` cannot be constructed from external automation; see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`); 2D drawing; additional mate types (parallel/perpendicular/concentric/angle/tangency); 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`); 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`. @@ -66,7 +66,7 @@ Key implementation facts (don't relearn): - **Patterns & mirror (package C)**: `linear_pattern` — `ksMeshCopyDefinition` via `ksPart.NewEntity(o3d_meshCopy=35)`; `SetAxis1(axis)` + `SetCopyParamAlongAxis(true,0,count,step,false)` + `count2=1` (disables 2nd direction → 1D linear); features added via `OperationArray()` (no Get). `circular_pattern` — `ksCircularCopyDefinition` via `NewEntity(o3d_circularCopy=36)`; `SetAxis(axis)`; properties set directly: `count1=1` (radial off), `count2=count` (ring), `step2=angle°` between neighbours, `factor2=false`, `inverce=reverse`; `GetOperationArray().Add(feature)`. `mirror_operation` — `ksMirrorCopyDefinition` via `NewEntity(o3d_mirrorOperation=48)`; `SetPlane(basePlane)` + `GetOperationArray().Add(feature)`. `mirror_body` — `ksMirrorCopyAllDefinition` via `NewEntity(o3d_mirrorAllOperation=49)`; `SetPlane(basePlane)`; `ChooseBodies()` does NOT cast to `ksChooseBodies` (returns null) and is not needed — `o3d_mirrorAllOperation` mirrors all bodies keeping the original (volume doubles). Source features retrieved by id from `_features` registry via new private `RequireFeature` helper (analogous to `RequireSketch`). New `src/Kompas.Mcp.Core/Modeling/CoordinateAxis.cs` — enum `CoordinateAxis {X,Y,Z}` + `CoordinateAxes.ToObj3dType/Parse` (→ `o3d_axisOX/OY/OZ = 71/72/73`), analogous to `BasePlane.cs`. `SketchGeometry.RequireMin(int value, int min, string paramName)` — validator for `count>=2`. Empirical: `count` includes the original instance (count=3 → 3 bodies); `count2=1` disables 2nd direction for linear; `step2` is angle° between neighbours for circular. - **Hole operations (`hole`, `hole_counterbore`, `hole_countersink`, `hole_conic`)**: all implemented via **API7** (not API5 — `ksHoleDefinition` absent in interop). Service `src/Kompas.Mcp.Core/Modeling/HoleService.cs`. `HoleCore` helper is parameterised with `ksHoleTypeEnum holeType` + `Action configure` callback (configure sets `HoleParameters` **after** `HoleType` is assigned, because the type-specific sub-interface — e.g. `ISpotfacingHoleParameters` — is not accessible until the type is fixed). Base `hole` = `ksHTBase` + `configure=null`. `hole_counterbore`: `ksHTCounterbore` + configure sets `(ISpotfacingHoleParameters)hole.HoleParameters` (`SpotfacingDiameter`, `SpotfacingDepth`); validates `spotDiameter>diameter` and `depth>spotDepth` for blind. `hole_countersink`: `ksHTCountersinking` + configure sets `(ICountersinkHoleParameters)hole.HoleParameters` (`CountersinkType=ksCTDiameterAngle`, `CountersinkDiameter`, `CountersinkAngle`); validates `sinkDiameter>diameter`, `0()` in `Program.cs`. +- **Assembly class (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`)**: `assembly_add_component` — workflow (API7): `IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)` → `IPart7` → `part.Placement` (`IPlacement3D`) → `SetOrigin(x,y,z)` → `part.UpdatePlacement(true)` → `top.RebuildModel(true)`. Empirical: `UpdatePlacement` returns FALSE for a manually-positioned component (no mates) — this is NOT an error. `RequireActiveAssembly` validates `doc.DocumentType == ksDocumentAssembly` BEFORE accessing `TopPart`. Rollback on failure: `RollbackInsertion` via `IFeature7.Delete + RebuildModel`. Returns `AddedComponent { Index, Name }`. `assembly_add_mate` — workflow (API7): `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)` → `BaseObject1/2` (faces via `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` (for distance) → `Update()` → `RebuildModel(true)`; check `mate.Valid` (false → error + rollback via `mate.Owner.Delete`). Own enum `MateType { Coincidence, Distance }` in `src/Kompas.Mcp.Core/Assemblies/MateType.cs` + static `Mates` (Parse/ToConstraintType/RequiresValue) mapping to КОМПАС `MateConstraintType` (`mc_Coincidence=0`, `mc_Distance=5`). Validator `AssemblyValidation.RequireMateValue` (distance `value>0`). Scope: `coincidence`/`distance` verified live; `parallel`/`perpendicular`/`concentric`/`angle`/`tangency` — future increments. DI: `AddSingleton()` in `Program.cs`. - **Richer sketch primitives (package A, ksDocument2D API5 wrappers)**: `ksArcBy3Points` (3-point arc), `ksArcByAngle` (centre/radius/start-end angles in degrees, counterClockwise flag), `ksEllipse` via `ksEllipseParam` — struct obtained via `KompasObject.GetParamStruct(ko_EllipseParam=22)`; **property names are `A`/`B` (uppercase) in the interop**; `ksRegularPolygon` via `ksRegularPolygonParam` (`ko_RegularPolygonParam=92`) — `describe = !inscribed`; NURBS spline via `ksNurbs(order=4)` + `ksNurbsPoint` loop + `ksEndObj`; `ksPoint`. Param structs are released after use. `PartModeler` split into partial classes: `PartModeler.cs` (registry/helpers/NewParam/reset), `PartModeler.Sketch.cs` (all 2D primitives), `PartModeler.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` class (`Core/Modeling`) holds enum mappings (`ArcDirection`, `PolygonDescribe`) and validators (`RequirePositive`, `RequireVertexCount`, `RequirePoints`). Polyline/spline point lists use `record SketchPoint(X, Y)` with JSON names `x`/`y`. ## КОМПАС-3D API architecture (the critical context) diff --git a/README.md b/README.md index 107f3b0..20aa249 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe } ``` -## Инструменты (71 инструмент) +## Инструменты (72 инструмента) | Группа | Инструменты | |---|---| @@ -53,7 +53,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe | 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` | -| Assembly | `assembly_add_component` | +| Assembly | `assembly_add_component`, `assembly_add_mate` | | Validation | `validate_part` | ### Типовой сценарий @@ -72,7 +72,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/ 138 тестов: unit + integration (integration требуют КОМПАС) +tests/Kompas.Mcp.Tests/ 156 тестов: unit + integration (integration требуют КОМПАС) libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common) docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/) usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8c0fe04..72a4dbe 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-D start «переменные»+package-E «offset plane»+`hole`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`). Актуализировано по коду. +> Статус: **реализовано и работает** (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`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`+`assembly_add_mate`). Актуализировано по коду. > Сопутствующий контекст по 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, измерения), `AssemblyService` (вставка компонентов в сборку, namespace `Kompas.Mcp.Core.Assemblies`), снимок. Все паттерны API5/API7 живут здесь. | -| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 138 тестов (83 unit + 55 integration). | +| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 156 тестов (98 unit + 58 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики. @@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async) --- -## 5. Карта инструментов (71 инструмент) +## 5. Карта инструментов (72 инструмента) Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`. @@ -206,6 +206,7 @@ MCP client ──stdio──> Host (поток-пул, async) **Assembly — построение сборок (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`)** - `assembly_add_component` — вставить компонент (.m3d деталь или .a3d подсборка) из файла в активную сборку; задать положение origin (x,y,z мм, мировая СК сборки). Возвращает индекс и имя вставленного компонента. Реализовано через API7: `IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)` → `IPart7` → `IPlacement3D.SetOrigin(x,y,z)` → `UpdatePlacement(true)` → `RebuildModel(true)`. `RequireActiveAssembly` проверяет `doc.DocumentType == ksDocumentAssembly` до обращения к `TopPart`. Откат при неуспехе через `IFeature7.Delete`. DI: `AddSingleton()` в `Program.cs`. +- `assembly_add_mate` — наложить сопряжение между гранями двух компонентов активной сборки по мировым точкам. Параметры: `mateType` (`coincidence` — грани заподлицо | `distance` — зазор `value` мм), `x1,y1,z1`, `x2,y2,z2`, `value=0`. Решатель позиционирует компоненты; возвращает статус `valid`. Реализовано через API7: `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)` → `BaseObject1/2` (грани через `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` → `Update()` → `RebuildModel(true)`; проверка `mate.Valid` (false → ошибка + откат `mate.Owner.Delete`). Enum `MateType { Coincidence, Distance }` в `src/Kompas.Mcp.Core/Assemblies/MateType.cs`; маппинг на `MateConstraintType` (`mc_Coincidence=0`, `mc_Distance=5`). Scope: `coincidence`/`distance` проверены вживую; прочие типы (`parallel`, `concentric`, …) — будущие инкременты. **Vision — визуальная обратная связь (fallback)** - `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**, @@ -340,7 +341,7 @@ dotnet build -c Release -r win-x64 # сборка ## 10. Дорожная карта -**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, 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); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`). +**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component+assembly_add_mate):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, 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); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`); **`assembly_add_mate`** (сопряжения `coincidence`/`distance`, API7, `IMateConstraints3D`). **Следующие приоритеты:** 1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D). ⚠️ **Исследовано**: `ksCDimWithVariable` недоступен из внешней автоматизации — см. `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`. @@ -348,7 +349,7 @@ dotnet build -c Release -r win-x64 # сборка 3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена. 4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`. 5. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты. -6. Сборки — продолжение: сопряжения (`IMates7`), ограничения, авто-позиционирование компонентов; спецификации (`ISpecification`). +6. Сборки — продолжение: дополнительные типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`; `coincidence`/`distance` реализованы); авто-позиционирование компонентов; спецификации (`ISpecification`). 7. Транспорт **HTTP/SSE** — как опция для удалённых клиентов. --- diff --git a/docs/OPEN_QUESTIONS.md b/docs/OPEN_QUESTIONS.md index 5d6900c..c050650 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -184,4 +184,7 @@ stateful-сессии? Пока полагаемся на последовате **Решено.** Новый инструмент `hole_conic` — коническое (конусное) отверстие, сужающееся вглубь. Параметры: `x,y,z`, `diameter` (диаметр у основания), `conicAngle` (угол конуса, 0
- Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component (71 инструмент) + Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate (72 инструмента)

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

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

-
Общий прогрессv3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · hole_counterbore · hole_countersink · hole_conic · draft (API5) · assembly_add_component · навык kompas-3d · 71 инструмент · 138 тестов
+
Общий прогрессv3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · hole_counterbore · hole_countersink · hole_conic · draft (API5) · assembly_add_component · assembly_add_mate · навык kompas-3d · 72 инструмента · 156 тестов
@@ -297,7 +297,7 @@

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

-

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

+

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

  • System: connect · status · set_visible
  • Documents: create · open · save · save_as · close · active
  • @@ -309,8 +309,8 @@
  • 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
  • -
  • Assembly: assembly_add_component (вставка компонента .m3d/.a3d в сборку, API7)
  • -
  • пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · сопряжения в сборках
  • +
  • Assembly: assembly_add_component (вставка компонента .m3d/.a3d в сборку, API7) · assembly_add_mate (сопряжения coincidence/distance, API7)
  • +
  • пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
@@ -321,7 +321,7 @@
  • Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText
  • Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция
  • -
  • Итого 138 тестов зелёных (83 unit + 55 integration)
  • +
  • Итого 156 тестов зелёных (98 unit + 58 integration)
  • Сборка kompas-mcp.exe, README с конфигом клиента
  • Навык kompas-3d (методика) + полигон usecases/
  • In-process тест MCP-клиента, релизный publish
  • @@ -348,8 +348,9 @@
    draft · готово ✓

    Уклон

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

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

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

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

    отверстия под крепёж · готово ✓

    Цековка, зенковка и конус

    hole_counterbore (ksHTCounterbore) · hole_countersink (ksHTCountersinking) · hole_conic (ksHTConic, IConicHoleParameters) · HoleCore рефакторен (configure-колбэк) · 70 инструментов · 119 тестов

    -
    сборки start · готово ✓

    Вставка компонентов

    assembly_add_component (API7 IParts7.AddFromFile + IPlacement3D) · 71 инструмент · 138 тестов

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

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

    вспомогательная геометрия (ось, угловая плоскость) · сопряжения в сборках · пакет D параметрика — параметрические эскизы недоступны через COM API

    +
    сборки start · готово ✓

    Вставка компонентов

    assembly_add_component (API7 IParts7.AddFromFile + IPlacement3D)

    +
    сборки инкремент 2 · готово ✓

    Сопряжения

    assembly_add_mate (coincidence/distance, API7 IMateConstraints3D) · IntegrationTestBase · 72 инструмента · 156 тестов

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

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

    вспомогательная геометрия (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API