From 775128484e7d954ea8adc68c44c877f83e837f70 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 21:47:17 +0300 Subject: [PATCH 1/4] =?UTF-8?q?feat(drawing):=20=D0=B0=D1=81=D1=81=D0=BE?= =?UTF-8?q?=D1=86=D0=B8=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D0=B0=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D0=B0=20=D0=B4=D0=B8=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE/=D1=80=D0=B0=D0=B4=D0=B8=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D0=BA=20=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=B8=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Флаг associate у drawing_add_diametral_dimension/drawing_add_radial_dimension: - associate=false (по умолчанию) — прежний свободный размер (без изменений); - associate=true — (xc,yc,radius) как ключ поиска спроецированной окружности (IDrawingContainer.Circles), привязка через BaseObject; значение Ø/R читается из геометрии и обновляется с моделью. - CircularObjectMatch.SelectMatchIndex (чистый отбор по центру+радиусу, допуск 1 мм; бросает при отсутствии/неоднозначности; концентрические разделяются радиусом) - RequireViewContainers (вид → ISymbols2DContainer + IDrawingContainer), CollectCircles, FindBaseCircle - 5 unit + 6 интеграционных тестов (всего 278 зелёных), сборка Release чистая Спайк подтвердил: BaseObject=circle без задания Xc/Yc/Radius → Valid, NominalValue из геометрии (Ø=20/R=10 для R10). Объём — только окружности (Circles); дуги/угловой/линейный — далее. Ревью Codex спека учтено (оба контейнера, guard вокруг блока, доказательство ассоциативности тестом radius=10.3→Ø20, регресс обоих свободных путей). Co-Authored-By: Claude Opus 4.7 (1M context) --- ...27-drawing-associative-dimension-design.md | 132 ++++++++++++++++ .../Drawings/CircularObjectMatch.cs | 44 ++++++ .../Drawings/DrawingService.cs | 69 +++++++-- src/Kompas.Mcp.Host/Tools/DrawingTools.cs | 28 ++-- .../CircularObjectMatchTests.cs | 53 +++++++ .../DrawingAssociativeDimensionTests.cs | 143 ++++++++++++++++++ 6 files changed, 450 insertions(+), 19 deletions(-) create mode 100644 docs/superpowers/specs/2026-05-27-drawing-associative-dimension-design.md create mode 100644 src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs create mode 100644 tests/Kompas.Mcp.Tests/CircularObjectMatchTests.cs create mode 100644 tests/Kompas.Mcp.Tests/Integration/DrawingAssociativeDimensionTests.cs diff --git a/docs/superpowers/specs/2026-05-27-drawing-associative-dimension-design.md b/docs/superpowers/specs/2026-05-27-drawing-associative-dimension-design.md new file mode 100644 index 0000000..926775f --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-drawing-associative-dimension-design.md @@ -0,0 +1,132 @@ +# Дизайн: ассоциативная привязка диаметрального и радиального размеров к геометрии вида (API7) + +**Дата:** 2026-05-27 +**Статус:** дизайн согласован (вариант A — флаг `associate`), спайк проведён, ревью Codex спека учтено — к реализации + +## Правки по ревью Codex (спек) + +- **#3/#4** объём сужен до **окружностей** (`IDrawingContainer.Circles`): спайк подтвердил привязку + только к окружности. Дуги (`Arcs`) — следующий инкремент со своим спайком (привязка `IArc` как + `BaseObject` для диам./радиального не проверена; дуги одного родителя неотличимы по центру+радиусу). +- **#1** вид резолвится один раз и приводится к обоим контейнерам: `ISymbols2DContainer` (создание + размера) И `IDrawingContainer` (поиск окружности). Новый helper `RequireViewContainers(viewNumber)` + → `(IView, ISymbols2DContainer, IDrawingContainer)`. +- **#2** весь блок после `…Dimensions.Add()` (присвоение `BaseObject`/`Angle`/`AutoNominalValue`, + `Update`, `Valid`, read-back) — внутри того же `try/catch` с `Delete` при любом сбое (как свободный путь). +- **#5** тест доказывает ассоциативность: ключ поиска `radius=10.3` (в допуске находит окружность R10) + → associate вернёт Ø=20 (из геометрии), свободный дал бы 2·10.3=20.6; `Value≈20` (не 20.6) доказывает + чтение из геометрии. + диагностический read-back `BaseObject != null`. +- **#6** обе ветки (свободная/associate) конвертируют `angle` через `DimensionAngles.ToRadians(angleDeg)` + перед присвоением `dim.Angle`. +- **#7** пост-инвариант значения (`value > 0 && double.IsFinite`) сохраняется и в associate-ветке (иначе откат). +- **#8** регресс-тесты свободного пути (`associate=false`) для ОБОИХ — диаметрального и радиального. + +## Цель + +Веха 2D-ЧЕРТЁЖ, инкремент 7. Размеры пока «свободные» (значение из заданных координат). Ассоциативная +привязка делает диаметральный/радиальный размер **связанным с проекцией геометрии**: значение (Ø/R) +читается из спроецированной окружности/дуги вида через `BaseObject` и обновляется вместе с моделью. +Первый шаг к «умному» чертежу. + +## Объём (обоснован рефлексией interop) + +Только **диаметральный** и **радиальный**, привязка только к **окружности** (`IDrawingContainer.Circles`) +— у `IDiametralDimension`/`IRadialDimension` есть свойство `BaseObject`; `_Circle` реализует +`IDrawingObject`, поэтому объект из коллекции присваивается `dim.BaseObject` (подтверждено спайком). +У `ILineDimension` `BaseObject` НЕТ; угловой требует `BaseObject1/2`; привязка к дуге (`Arcs`) не +проверена — всё это отдельные инкременты (вне объёма). + +## Спайк: механизм подтверждён вживую (НЕ разучивать) + +Спайк (`_SpikeAssocDim`, прогнан на реальном КОМПАС, затем удалён): цилиндр (окружность R10, выдавлен +на 20) → стандартные виды. Один из видов содержит спроецированную окружность. + +``` +IDrawingContainer dc = (IDrawingContainer)view; // QI от вида (как ISymbols2DContainer) +ICircles circles = dc.Circles; // Count=1; circles[0] as ICircle → Xc=0,Yc=0,R=10 +IDiametralDimension dia = symbols.DiametralDimensions.Add(); +dia.BaseObject = circles[0]; // IDrawingObject; Xc/Yc/Radius НЕ задаём +dia.Angle = π/4; // направление выноски (размещение) +dia.Update(); // True, Valid=True +double v = ((IDimensionText)dia).NominalValue; // == 20 — ДИАМЕТР, прочитан из геометрии! +``` + +**Проверено:** `BaseObject=circle` без задания координат → `Update=True`, `Valid=True`, +диаметральный `NominalValue=20` (=2·R геометрии), радиальный `NominalValue=10` (=R). `BaseObject` +остаётся непустым после. Геометрия вида: `IDrawingContainer.Circles` (`ICircle`: `Xc/Yc/Radius`), +`.Arcs` (`IArc`: `Xc/Yc/Radius/Angle1/Angle2`), `.LineSegments`. **Нет** «find by point» — выбор +перебором коллекции. + +## MCP-инструменты (расширение существующих, вариант A) + +К `drawing_add_diametral_dimension` и `drawing_add_radial_dimension` добавляется параметр +`associate=false`: + +| `associate` | Поведение | +|---|---| +| `false` (по умолчанию) | Текущее «свободное»: `Xc/Yc/Radius` задаются напрямую (без изменений, обратная совместимость). | +| `true` | `(xc,yc,radius)` — **ключ поиска**: найти в виде окружность/дугу с центром ≈ (xc,yc) и радиусом ≈ radius, привязать `BaseObject`; значение (Ø/R) читается из геометрии. | + +Координаты ключа — view-local (= координаты модели при масштабе 1:1; при ином масштабе — масштабированные, +как у прочих размеров). `angle` — направление выноски (°), как сейчас. `viewNumber` — как сейчас. + +## Выбор объекта + +В `IDrawingContainer` нет «find by point». Перебираем `Circles`, собираем кандидатов `(Xc, Yc, Radius)`, +ищем подходящий по ключу `(xc, yc, radius)`: +- кандидат подходит, если `dist(центр, ключ) ≤ tol` И `|R − radius| ≤ tol` (`tol = 1.0 мм`, константа); +- ровно 1 подходящий → привязываем; 0 → ошибка «окружность не найдена рядом с (xc,yc) R≈radius»; + >1 → ошибка «неоднозначно — уточните координаты/радиус». + +Радиус в ключе снимает неоднозначность концентрических окружностей (цековка R5/R10 — ключ R10 +выберет только внешнюю). Логика отбора — чистая (unit-тест), перечисление COM — в сервисе. + +## Архитектура + +- `DrawingService.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angleDeg, associate, ct)` и + `AddRadialDimensionAsync(..., associate, ct)` — добавлен `associate` (по умолчанию `false`). При `false` — + текущая ветка без изменений. Обе ветки: `angleRad = DimensionAngles.ToRadians(angleDeg)`. +- При `associate=true`: `RequireViewContainers(viewNumber)` → собрать кандидатов из `Circles` + (`CollectCircles`) → `CircularObjectMatch.SelectMatchIndex(...)` → взять `IDrawingObject` по индексу → + `symbols.{Diametral|Radial}Dimensions.Add()` → **внутри `try/catch`+`Delete`**: `dim.BaseObject = obj`, + `dim.Angle = angleRad`, `AutoNominalValue=true` → `Update()` (FALSE→откат) → `Valid` (false→откат) → + `value = NominalValue` (из геометрии); `value<=0 || !IsFinite` → откат → `DrawingDimensionResult`. +- Новый helper `RequireViewContainers(viewNumber)` → `(IView View, ISymbols2DContainer Symbols, + IDrawingContainer Drawing)` — резолвит вид один раз, оба QI с раздельной диагностикой (переиспользует + `FindView`). +- Новый файл `src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs`: чистый статический + `record CircleCandidate(double Xc, double Yc, double Radius)` + `int SelectMatchIndex(IReadOnlyList + candidates, double keyX, double keyY, double keyR, double tol)` — возвращает индекс единственного + совпадения; `InvalidOperationException` при 0 (нет) / >1 (неоднозначно). +- Сбор кандидатов из COM (Circles в список с сохранением ссылки на `IDrawingObject` + геометрии) — в + сервисе (private helper `CollectCircles(IDrawingContainer)` → `List<(IDrawingObject Obj, CircleCandidate Geom)>`). +- `DrawingDimensionResult` переиспользуется (значение из геометрии). Свободный путь не трогаем. + +## Валидация + +- Оба пути: `RequireFiniteCoords(xc, yc, angleDeg)` + `RequirePositiveRadius(radius)` (есть). В + associate-режиме radius — положительный ключ поиска. +- `CircularObjectMatch.SelectMatchIndex` — чистая логика отбора (unit). + +## Тестирование + +### Unit (`CircularObjectMatchTests`) +- Единственное совпадение по центру+радиусу → индекс. +- Нет кандидатов в допуске → `InvalidOperationException` (нет). +- Два совпадения в допуске → `InvalidOperationException` (неоднозначно). +- Концентрические (один центр, разный R): ключ по R выбирает нужный (не неоднозначно). +- Пустой список → `InvalidOperationException`. + +### Integration (`DrawingAssociativeDimensionTests`; модель — цилиндр R10, как в спайке) +1. **Диаметральный associative + доказательство геометрии**: ключ `(0,0,10.3)` (radius в допуске + находит окружность R10) → `Value ≈ 20` (Ø из геометрии, НЕ 2·10.3=20.6 — доказывает чтение из + геометрии); попадание в вид (`DiametralDimensions.Count` +1). +2. **Радиальный associative**: `(0,0,10.3)` → `Value ≈ 10` (R из геометрии, не 10.3). +3. **Нет окружности рядом**: ключ далеко от геометрии → `InvalidOperationException`; счётчик не вырос (откат). +4. **Свободный путь не сломан** — диаметральный `associate=false` `(xc,yc,15)` → `Value ≈ 30`. +5. **Свободный путь не сломан** — радиальный `associate=false` `(xc,yc,12)` → `Value ≈ 12` (регресс обоих). +6. **Нет видов** (associate) → понятная ошибка. + +## Дальнейшее (вне спека) +- Ассоциативная привязка углового (`BaseObject1/2` — пара отрезков) и шероховатости (`IRough.BaseObject`); + линейный — `BaseObject` отсутствует (нужен иной механизм). Рамка/формат листа; выноски/базы/допуски формы. diff --git a/src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs b/src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs new file mode 100644 index 0000000..e9bc09f --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs @@ -0,0 +1,44 @@ +namespace Kompas.Mcp.Core.Drawings; + +/// Геометрия окружности-кандидата вида (центр + радиус, локальная СК вида, мм). +public sealed record CircleCandidate(double Xc, double Yc, double Radius); + +/// +/// Чистый отбор окружности проекционного вида по ключу (центр + радиус) для ассоциативной привязки +/// размера. Выделен из COM-перечисления ради unit-тестов. +/// +public static class CircularObjectMatch +{ + /// + /// Индекс единственной окружности из , чей центр и радиус совпадают с + /// ключом (,,) в пределах + /// (мм): dist(центр,ключ) ≤ tol И |R − keyR| ≤ tol. + /// + /// Нет подходящих (0) или неоднозначно (>1). + public static int SelectMatchIndex(IReadOnlyList candidates, + double keyX, double keyY, double keyR, double tol) + { + ArgumentNullException.ThrowIfNull(candidates); + + var matches = new List(); + for (var i = 0; i < candidates.Count; i++) + { + var c = candidates[i]; + var dx = c.Xc - keyX; + var dy = c.Yc - keyY; + if (Math.Sqrt(dx * dx + dy * dy) <= tol && Math.Abs(c.Radius - keyR) <= tol) + matches.Add(i); + } + + return matches.Count switch + { + 1 => matches[0], + 0 => throw new InvalidOperationException( + $"Окружность не найдена рядом с ({keyX},{keyY}) R≈{keyR} (допуск {tol} мм). " + + "Проверьте координаты/радиус и наличие окружности на этом виде."), + _ => throw new InvalidOperationException( + $"Неоднозначный выбор окружности у ({keyX},{keyY}) R≈{keyR}: подходит {matches.Count}. " + + "Уточните координаты/радиус."), + }; + } +} diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs index 00d0224..3a3eb4d 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -142,22 +142,27 @@ public sealed class DrawingService /// Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида. /// public Task AddDiametralDimensionAsync(int viewNumber, double xc, double yc, - double radius, double angleDeg, CancellationToken ct = default) - => _dispatcher.InvokeAsync(() => AddDiametralDimensionCore(viewNumber, xc, yc, radius, angleDeg), ct); + double radius, double angleDeg, bool associate = false, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => AddDiametralDimensionCore(viewNumber, xc, yc, radius, angleDeg, associate), ct); private DrawingDimensionResult AddDiametralDimensionCore(int viewNumber, double xc, double yc, - double radius, double angleDeg) + double radius, double angleDeg, bool associate) { DrawingValidation.RequireFiniteCoords(xc, yc); DrawingValidation.RequirePositiveRadius(radius); var angleRad = DimensionAngles.ToRadians(angleDeg); - var (view, symbols) = RequireSymbols2DContainer(viewNumber); + var (view, symbols, drawing) = RequireViewContainers(viewNumber); + var baseObject = associate ? FindBaseCircle(drawing, xc, yc, radius) : null; var dim = symbols.DiametralDimensions?.Add() ?? throw new InvalidOperationException("DiametralDimensions.Add вернул null."); try { - dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; dim.Angle = angleRad; + if (baseObject != null) + dim.BaseObject = baseObject; // ассоциативно: значение Ø читается из геометрии + else + { dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; } + dim.Angle = angleRad; // DimensionType (стиль размерной линии) не задаём — оставляем COM-дефолт (полярность bool // не подтверждена; дефолт даёт валидный размер с верным значением — проверено спайком). ((IDimensionText)dim).AutoNominalValue = true; @@ -190,22 +195,27 @@ public sealed class DrawingService /// Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида. /// public Task AddRadialDimensionAsync(int viewNumber, double xc, double yc, - double radius, double angleDeg, CancellationToken ct = default) - => _dispatcher.InvokeAsync(() => AddRadialDimensionCore(viewNumber, xc, yc, radius, angleDeg), ct); + double radius, double angleDeg, bool associate = false, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => AddRadialDimensionCore(viewNumber, xc, yc, radius, angleDeg, associate), ct); private DrawingDimensionResult AddRadialDimensionCore(int viewNumber, double xc, double yc, - double radius, double angleDeg) + double radius, double angleDeg, bool associate) { DrawingValidation.RequireFiniteCoords(xc, yc); DrawingValidation.RequirePositiveRadius(radius); var angleRad = DimensionAngles.ToRadians(angleDeg); - var (view, symbols) = RequireSymbols2DContainer(viewNumber); + var (view, symbols, drawing) = RequireViewContainers(viewNumber); + var baseObject = associate ? FindBaseCircle(drawing, xc, yc, radius) : null; var dim = symbols.RadialDimensions?.Add() ?? throw new InvalidOperationException("RadialDimensions.Add вернул null."); try { - dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; dim.Angle = angleRad; + if (baseObject != null) + dim.BaseObject = baseObject; // ассоциативно: значение R читается из геометрии + else + { dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; } + dim.Angle = angleRad; dim.DimensionType = true; // стиль R «от центра» (на величину не влияет — спайк) ((IDimensionText)dim).AutoNominalValue = true; @@ -429,6 +439,45 @@ public sealed class DrawingService return (view, container); } + /// Допуск совпадения окружности с ключом поиска при ассоциативной привязке (мм). + private const double CircleMatchToleranceMm = 1.0; + + /// Вид + оба контейнера (QI от одного вида): ISymbols2DContainer (создание размера) и + /// IDrawingContainer (поиск геометрии для ассоциативной привязки). + private (IView View, ISymbols2DContainer Symbols, IDrawingContainer Drawing) RequireViewContainers(int viewNumber) + { + var view = FindView(viewNumber); + var symbols = view as ISymbols2DContainer + ?? throw new InvalidOperationException("Вид не приводится к ISymbols2DContainer."); + var drawing = view as IDrawingContainer + ?? throw new InvalidOperationException("Вид не приводится к IDrawingContainer."); + return (view, symbols, drawing); + } + + /// Найти окружность вида по ключу (центр + радиус) и вернуть её как IDrawingObject + /// (для dim.BaseObject). Бросает при отсутствии/неоднозначности. + private static IDrawingObject FindBaseCircle(IDrawingContainer drawing, double xc, double yc, double radius) + { + var items = CollectCircles(drawing); + var index = CircularObjectMatch.SelectMatchIndex( + items.Select(i => i.Geom).ToList(), xc, yc, radius, CircleMatchToleranceMm); + return items[index].Obj; + } + + /// Собрать окружности вида (IDrawingContainer.Circles) с их геометрией и COM-ссылкой. + private static List<(IDrawingObject Obj, CircleCandidate Geom)> CollectCircles(IDrawingContainer drawing) + { + var result = new List<(IDrawingObject, CircleCandidate)>(); + var circles = drawing.Circles; + if (circles == null) return result; + for (var i = 0; i < circles.Count; i++) + { + if (circles[i] is not ICircle c) continue; + result.Add(((IDrawingObject)c, new CircleCandidate(c.Xc, c.Yc, c.Radius))); + } + return result; + } + /// Вид + его ISymbols2DContainer (QI от вида). Общий для линейных/диаметральных/… размеров. private (IView View, ISymbols2DContainer Symbols) RequireSymbols2DContainer(int viewNumber) { diff --git a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs index a729a70..966a8d0 100644 --- a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs +++ b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs @@ -71,35 +71,45 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing) "задаётся центром (xc,yc) и радиусом radius в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты " + "модели на плоскости вида) — задайте параметры реальной окружности вида. angle — направление выноски " + "в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). " + - "Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида.")] + "associate=false: свободный размер (значение = 2·radius). associate=true: АССОЦИАТИВНО — (xc,yc,radius) " + + "используются как ключ для поиска спроецированной окружности на виде; размер привязывается к ней " + + "(BaseObject) и значение Ø читается из геометрии и обновляется с моделью. Значение измеряется " + + "автоматически. Возвращает диаметр и номер вида.")] public async Task AddDiametralDimension( [Description("X центра окружности (СК вида, мм)")] double xc, [Description("Y центра окружности")] double yc, [Description("Радиус окружности, мм")] double radius, [Description("Направление выноски, градусы")] double angle = 0, - [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0) + [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0, + [Description("true = привязать к окружности вида (значение из геометрии); false = свободный размер")] bool associate = false) { await session.ConnectAsync(); - var r = await drawing.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angle); - return $"Диаметральный размер поставлен на виде {r.ViewNumber}, Ø{r.Value:0.###} мм."; + var r = await drawing.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angle, associate); + var mode = associate ? " (ассоциативно)" : ""; + return $"Диаметральный размер поставлен на виде {r.ViewNumber}, Ø{r.Value:0.###} мм{mode}."; } [McpServerTool(Name = "drawing_add_radial_dimension")] - [Description("Поставить радиальный размер (R) окружности/дуги на виде активного чертежа. Окружность " + + [Description("Поставить радиальный размер (R) окружности на виде активного чертежа. Окружность " + "задаётся центром (xc,yc) и радиусом radius в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты " + "модели на плоскости вида) — задайте параметры реальной окружности вида. angle — направление выноски " + "в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). " + - "Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида.")] + "associate=false: свободный размер (значение = radius). associate=true: АССОЦИАТИВНО — (xc,yc,radius) " + + "используются как ключ для поиска спроецированной окружности на виде; размер привязывается к ней " + + "(BaseObject) и значение R читается из геометрии и обновляется с моделью. Значение измеряется " + + "автоматически. Возвращает радиус и номер вида.")] public async Task AddRadialDimension( [Description("X центра окружности (СК вида, мм)")] double xc, [Description("Y центра окружности")] double yc, [Description("Радиус окружности, мм")] double radius, [Description("Направление выноски, градусы")] double angle = 0, - [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0) + [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0, + [Description("true = привязать к окружности вида (значение из геометрии); false = свободный размер")] bool associate = false) { await session.ConnectAsync(); - var r = await drawing.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angle); - return $"Радиальный размер поставлен на виде {r.ViewNumber}, R{r.Value:0.###} мм."; + var r = await drawing.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angle, associate); + var mode = associate ? " (ассоциативно)" : ""; + return $"Радиальный размер поставлен на виде {r.ViewNumber}, R{r.Value:0.###} мм{mode}."; } [McpServerTool(Name = "drawing_add_angular_dimension")] diff --git a/tests/Kompas.Mcp.Tests/CircularObjectMatchTests.cs b/tests/Kompas.Mcp.Tests/CircularObjectMatchTests.cs new file mode 100644 index 0000000..c2e960f --- /dev/null +++ b/tests/Kompas.Mcp.Tests/CircularObjectMatchTests.cs @@ -0,0 +1,53 @@ +using Kompas.Mcp.Core.Drawings; + +namespace Kompas.Mcp.Tests; + +[Trait("Category", "Unit")] +public sealed class CircularObjectMatchTests +{ + private const double Tol = 1.0; + + private static IReadOnlyList Candidates(params (double x, double y, double r)[] items) + => items.Select(i => new CircleCandidate(i.x, i.y, i.r)).ToList(); + + [Fact] + public void SelectMatchIndex_returns_unique_match() + { + var c = Candidates((0, 0, 10), (50, 20, 5)); + // ключ с допустимым отклонением центра и радиуса + Assert.Equal(0, CircularObjectMatch.SelectMatchIndex(c, 0.3, -0.2, 10.3, Tol)); + Assert.Equal(1, CircularObjectMatch.SelectMatchIndex(c, 50, 20, 5, Tol)); + } + + [Fact] + public void SelectMatchIndex_concentric_disambiguated_by_radius() + { + // концентрические R5 и R10 (цековка): ключ R10 выбирает внешнюю, не неоднозначно + var c = Candidates((0, 0, 5), (0, 0, 10)); + Assert.Equal(1, CircularObjectMatch.SelectMatchIndex(c, 0, 0, 10, Tol)); + Assert.Equal(0, CircularObjectMatch.SelectMatchIndex(c, 0, 0, 5, Tol)); + } + + [Fact] + public void SelectMatchIndex_throws_when_none_in_tolerance() + { + var c = Candidates((0, 0, 10)); + // центр далеко + Assert.Throws(() => CircularObjectMatch.SelectMatchIndex(c, 100, 100, 10, Tol)); + // радиус далеко + Assert.Throws(() => CircularObjectMatch.SelectMatchIndex(c, 0, 0, 25, Tol)); + } + + [Fact] + public void SelectMatchIndex_throws_when_ambiguous() + { + // две почти совпадающие окружности в допуске + var c = Candidates((0, 0, 10), (0.2, 0.2, 10.2)); + Assert.Throws(() => CircularObjectMatch.SelectMatchIndex(c, 0, 0, 10, Tol)); + } + + [Fact] + public void SelectMatchIndex_throws_on_empty() + => Assert.Throws( + () => CircularObjectMatch.SelectMatchIndex(Candidates(), 0, 0, 10, Tol)); +} diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingAssociativeDimensionTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingAssociativeDimensionTests.cs new file mode 100644 index 0000000..cb834b3 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingAssociativeDimensionTests.cs @@ -0,0 +1,143 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; +using Kompas.Mcp.Core.Modeling; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: ассоциативная привязка диаметрального/радиального размера к окружности вида +/// (BaseObject). Модель — цилиндр R10 (вид проецирует окружность). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class DrawingAssociativeDimensionTests : IntegrationTestBase +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly DrawingService _drawing; + + public DrawingAssociativeDimensionTests(KompasFixture fx) : base(fx) + { + _docs = new DocumentService(fx.Session, fx.Dispatcher); + _modeler = new PartModeler(fx.Session, fx.Dispatcher); + _drawing = new DrawingService(fx.Session, fx.Dispatcher); + } + + private async Task BuildAndSaveCylinderAsync() + { + var path = TestPaths.NewFile(".m3d"); + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + var s = await _modeler.OpenSketchAsync(BasePlane.XOY); + await _modeler.AddCircleAsync(s, 0, 0, 10); + await _modeler.CloseSketchAsync(s); + await _modeler.ExtrudeAsync(s, depth: 20); + await _modeler.RebuildAsync(); + await _docs.SaveAsAsync(path); + } + finally { await _docs.CloseAsync(save: false); } + return path; + } + + [Fact] + public async Task Diametral_associative_reads_diameter_from_geometry() + { + var partFile = await BuildAndSaveCylinderAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var target = views.ViewNumbers[0]; // вид с окружностью (спайк) + + var before = await _drawing.GetViewDiametralDimensionCountAsync(target); + // Ключ radius=10.3 (в допуске 1.0 находит окружность R10). Ассоциативно → Ø=20 из геометрии, + // а НЕ 2·10.3=20.6 → доказывает чтение значения из геометрии, а не из входа. + var r = await _drawing.AddDiametralDimensionAsync(target, xc: 0, yc: 0, radius: 10.3, + angleDeg: 45, associate: true); + var after = await _drawing.GetViewDiametralDimensionCountAsync(target); + + Assert.Equal(target, r.ViewNumber); + Assert.InRange(r.Value, 19.9, 20.1); // Ø=20 из геометрии (не 20.6) + Assert.Equal(before + 1, after); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Radial_associative_reads_radius_from_geometry() + { + var partFile = await BuildAndSaveCylinderAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var target = views.ViewNumbers[0]; + var r = await _drawing.AddRadialDimensionAsync(target, xc: 0, yc: 0, radius: 10.3, + angleDeg: -45, associate: true); + Assert.InRange(r.Value, 9.9, 10.1); // R=10 из геометрии (не 10.3) + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Associative_no_circle_near_throws_and_no_orphan() + { + var partFile = await BuildAndSaveCylinderAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var target = views.ViewNumbers[0]; + var before = await _drawing.GetViewDiametralDimensionCountAsync(target); + // Ключ далеко от любой окружности → отбор бросит до Add (размер не создаётся). + await Assert.ThrowsAsync(() => + _drawing.AddDiametralDimensionAsync(target, xc: 1000, yc: 1000, radius: 5, + angleDeg: 0, associate: true)); + var after = await _drawing.GetViewDiametralDimensionCountAsync(target); + Assert.Equal(before, after); // нет «осиротевшего» размера + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Free_diametral_path_unchanged() + { + var partFile = await BuildAndSaveCylinderAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + // associate=false (по умолчанию) — свободный размер: значение = 2·radius. + var r = await _drawing.AddDiametralDimensionAsync(views.ViewNumbers[0], xc: 0, yc: 0, + radius: 15, angleDeg: 0, associate: false); + Assert.InRange(r.Value, 29.9, 30.1); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Free_radial_path_unchanged() + { + var partFile = await BuildAndSaveCylinderAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var r = await _drawing.AddRadialDimensionAsync(views.ViewNumbers[0], xc: 0, yc: 0, + radius: 12, angleDeg: 0, associate: false); + Assert.InRange(r.Value, 11.9, 12.1); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Associative_no_views_throws() + { + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await Assert.ThrowsAsync(() => + _drawing.AddDiametralDimensionAsync(0, xc: 0, yc: 0, radius: 10, angleDeg: 0, associate: true)); + } + finally { await _docs.CloseAsync(save: false); } + } +} From d3b3a7d58990438eec0feb5692cbe139314ba51a Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 21:52:35 +0300 Subject: [PATCH 2/4] =?UTF-8?q?review(drawing):=20=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?Codex=20(=D0=B0=D1=81=D1=81=D0=BE=D1=86=D0=B8=D0=B0=D1=82=D0=B8?= =?UTF-8?q?=D0=B2=D0=BD=D0=B0=D1=8F=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7?= =?UTF-8?q?=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - свободный путь (associate=false) берёт только ISymbols2DContainer (как раньше) — IDrawingContainer запрашивается лишь при associate=true (не расширяем поверхность отказа) - XML-доки диаметрального/радиального уточнены: при associate значение из геометрии 16 тестов привязки+регрессии зелёные, сборка Release чистая. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Drawings/DrawingService.cs | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs index 3a3eb4d..10e8aaa 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -137,9 +137,11 @@ public sealed class DrawingService => _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.LineDimensions?.Count ?? 0, ct); /// - /// Поставить диаметральный размер (Ø) окружности с центром (xc,yc) и радиусом - /// (ЛОКАЛЬНАЯ СК вида, мм) на виде. — направление выноски (градусы). - /// Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида. + /// Поставить диаметральный размер (Ø) окружности на виде. — направление + /// выноски (градусы). При =false — свободный размер по (xc,yc,radius), + /// значение = 2·radius. При =true — (xc,yc,radius) служат ключом поиска + /// спроецированной окружности (Circles), размер привязывается к ней (BaseObject), значение Ø читается + /// из геометрии. Координаты — ЛОКАЛЬНАЯ СК вида (мм). Возвращает диаметр и номер вида. /// public Task AddDiametralDimensionAsync(int viewNumber, double xc, double yc, double radius, double angleDeg, bool associate = false, CancellationToken ct = default) @@ -152,8 +154,20 @@ public sealed class DrawingService DrawingValidation.RequirePositiveRadius(radius); var angleRad = DimensionAngles.ToRadians(angleDeg); - var (view, symbols, drawing) = RequireViewContainers(viewNumber); - var baseObject = associate ? FindBaseCircle(drawing, xc, yc, radius) : null; + // Свободный путь берёт только ISymbols2DContainer (как раньше); IDrawingContainer (геометрия) + // нужен лишь для ассоциативной привязки — не расширяем поверхность отказа свободного пути. + IView view; ISymbols2DContainer symbols; IDrawingObject? baseObject = null; + if (associate) + { + var c = RequireViewContainers(viewNumber); + view = c.View; symbols = c.Symbols; + baseObject = FindBaseCircle(c.Drawing, xc, yc, radius); + } + else + { + var c = RequireSymbols2DContainer(viewNumber); + view = c.View; symbols = c.Symbols; + } var dim = symbols.DiametralDimensions?.Add() ?? throw new InvalidOperationException("DiametralDimensions.Add вернул null."); try @@ -190,9 +204,11 @@ public sealed class DrawingService => _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.DiametralDimensions?.Count ?? 0, ct); /// - /// Поставить радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом - /// (ЛОКАЛЬНАЯ СК вида, мм) на виде. — направление выноски (градусы). - /// Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида. + /// Поставить радиальный размер (R) окружности на виде. — направление + /// выноски (градусы). При =false — свободный размер по (xc,yc,radius), + /// значение = radius. При =true — (xc,yc,radius) служат ключом поиска + /// спроецированной окружности (Circles), размер привязывается к ней (BaseObject), значение R читается + /// из геометрии. Координаты — ЛОКАЛЬНАЯ СК вида (мм). Возвращает радиус и номер вида. /// public Task AddRadialDimensionAsync(int viewNumber, double xc, double yc, double radius, double angleDeg, bool associate = false, CancellationToken ct = default) @@ -205,8 +221,20 @@ public sealed class DrawingService DrawingValidation.RequirePositiveRadius(radius); var angleRad = DimensionAngles.ToRadians(angleDeg); - var (view, symbols, drawing) = RequireViewContainers(viewNumber); - var baseObject = associate ? FindBaseCircle(drawing, xc, yc, radius) : null; + // Свободный путь берёт только ISymbols2DContainer (как раньше); IDrawingContainer (геометрия) + // нужен лишь для ассоциативной привязки — не расширяем поверхность отказа свободного пути. + IView view; ISymbols2DContainer symbols; IDrawingObject? baseObject = null; + if (associate) + { + var c = RequireViewContainers(viewNumber); + view = c.View; symbols = c.Symbols; + baseObject = FindBaseCircle(c.Drawing, xc, yc, radius); + } + else + { + var c = RequireSymbols2DContainer(viewNumber); + view = c.View; symbols = c.Symbols; + } var dim = symbols.RadialDimensions?.Add() ?? throw new InvalidOperationException("RadialDimensions.Add вернул null."); try From ecf2e22265efe4fcbae5a47121e0aa2554ea1d8d Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 21:55:12 +0300 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20=D1=81=D0=B6=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20CLAUDE.md=20(=E2=88=9236%=20?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Каталог инструментов из «Current state» свёрнут в указатель на README §Инструменты (единый источник правды); дубль-каталог «are done» / «not yet done» убран в пользу ARCHITECTURE §10 и TODO.md. Уникальные COM-гочи из него перенесены в структурированный bullet про Drawing. В bullets «don't relearn» подрезан boilerplate (имена файлов, DI, валидаторы) — все COM-цепочки и эмпирика сохранены. Исправлена модель субагента kompas-sdk-research: Sonnet на Haiku (рассинхрон с определением агента). Включает текущие инкрементальные правки (счётчики тестов, заметка об ассоциативной привязке размеров). Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 81 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4742496..2c2ba3b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Build an **MCP (Model Context Protocol) server** that lets an LLM drive the CAD system **КОМПАС-3D** (ASCON). The server exposes КОМПАС operations (create/open documents, build 2D geometry, 3D parts and assemblies, read/write parameters, run macros) as MCP tools. Authoritative references: -- **SDK knowledge base in this repo: `docs/Kompas3D_SDK/`** — clean Markdown, one article per COM interface, with YAML tags; search it with Grep/Read (see below). Committed to the repo (canonical), distilled from the КОМПАС SDK help. Prefer this over the web. For non-trivial lookups, dispatch the **`kompas-sdk-research`** subagent (Sonnet) to save context. +- **SDK knowledge base in this repo: `docs/Kompas3D_SDK/`** — clean Markdown, one article per COM interface, with YAML tags; search it with Grep/Read (see below). Committed to the repo (canonical), distilled from the КОМПАС SDK help. Prefer this over the web. For non-trivial lookups, dispatch the **`kompas-sdk-research`** subagent (Haiku) to save context. - Online SDK docs: https://help.ascon.ru/KOMPAS_SDK/22/ru-RU/index.html - Local SDK install: `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK` @@ -23,12 +23,15 @@ The base is committed to the repo (canonical) — no regeneration step. It cover ## Current state -**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**. -**81 MCP tools, 267 tests green (163 unit + 104 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 мм). **2D-чертёж (веха, инкремент 3):** **`drawing_create_standard_views`** (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`) — создать стандартные ассоциативные виды (спереди/сверху/слева) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже; возвращает число созданных видов (3) и их номера (`ViewNumbers`). **`drawing_fill_title_block`** — заполнить графы основной надписи (штампа) активного чертежа: `designation` (обозначение, графа 2), `name` (наименование изделия, графа 1), `material` (материал, графа 3); передаются только нужные поля (null/пусто пропускается, нужно хотя бы одно); возвращает число заполненных граф. **`drawing_add_linear_dimension`** — поставить линейный размер на виде активного чертежа: точки (x1,y1)-(x2,y2) в локальной СК вида (мм), положение размерной линии (x3,y3), `orientation` (horizontal|vertical|parallel), `viewNumber` (0=первый вид); значение измеряется автоматически; возвращает значение и номер вида. **`drawing_add_diametral_dimension`** — поставить диаметральный размер (Ø) окружности: центр (xc,yc), `radius`, `angle` (направление выноски, градусы), `viewNumber`; значение = 2·radius, измеряется автоматически; возвращает диаметр и номер вида. **2D-чертёж (инкремент 5, базовое семейство размеров завершено):** **`drawing_add_radial_dimension`** — радиальный размер (R) окружности/дуги на виде: центр (xc,yc), `radius`, `angle` (направление выноски, °), `viewNumber`; значение измеряется автоматически и = **РАДИУСУ** (не диаметру — вопреки справке SDK); возвращает радиус и номер вида. **`drawing_add_angular_dimension`** — угловой размер между лучами (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) на виде: (x3,y3) — положение размерной дуги (задаёт её радиус), `angleType` (`min`|`max`|`more`) выбирает измеряемый угол, `viewNumber`; значение (градусы) измеряется автоматически; возвращает угол (°) и номер вида. **2D-чертёж (инкремент 6, текстовые обозначения):** **`drawing_add_rough`** — знак шероховатости на виде: `x,y` (положение, ЛСК вида, мм), `value` (текст параметра, напр. "Ra 1.6"; пусто = без значения), `signType` (`delete`|`without`|`none`), `angle` (наклон оси, °), `viewNumber`; возвращает значение (read-back) и номер вида. **`drawing_add_text`** — свободная текстовая надпись на виде стилем по умолчанию: `x,y` (точка привязки), `text` (`\n` — многострочно), `angle`, `viewNumber`; возвращает текст (read-back) и номер вида. **`drawing_set_technical_requirements`** — технические требования чертежа (единый блок над штампом, уровень документа): `text` (строки через `\n`); перезаписывает прежние; возвращает число строк. -See [`README.md`](README.md) and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); design decisions/caveats in -[`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). **Remaining stages backlog: [`docs/TODO.md`](docs/TODO.md)** -(canonical list of what's left, by class + cross-cutting; top of «2D-ЧЕРТЁЖ» = next priority). +**Status:** v1 + v2 + STEP/assembly + direct B-rep edit + structural inspection + 2D drawings — 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**. **81 MCP tools, 278 tests green (168 unit + 110 integration).** + +Where to look (single source of truth — do **not** duplicate these lists here): +- **Full tool catalog** (by group, all 81) → [`README.md`](README.md) §«Инструменты». +- **Roadmap / what's already done** → [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §10. +- **Backlog / what's left** → [`docs/TODO.md`](docs/TODO.md) (canonical; top of «2D-ЧЕРТЁЖ» = next priority. Associative diametral/radial→circle binding done via `associate` flag — next: arc/angular/rough binding, sheet format, leaders, bases, tolerances; plus extra mate types, package E continuation). +- **Design decisions / caveats** → [`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). Known caveat («Ревью v2»): boss/cut direction on a selected face depends on face-normal orientation — `forward` may need flipping; the agent picks via snapshot or `describe_face` normal. +- **Verified COM call-chains & gotchas** → «Key implementation facts» below + memory files (`kompas-*-api*.md`). Principle: **MCP = translating SDK capabilities into general tools** (not task-specific). On top of MCP — skill **`.claude/skills/kompas-3d/`** with a methodology (playbooks, heuristics, validated in `usecases/`). Layout: `usecases/` (in .gitignore) is the proving ground; proven techniques are promoted to the skill. @@ -47,33 +50,45 @@ dotnet test --filter "Category=Integration" # integration (requires running dotnet run --project src/Kompas.Mcp.Host # start the MCP server (stdio) ``` -Key implementation facts (don't relearn): +## Key implementation facts (don't relearn) + - **All COM calls run on one dedicated STA thread** (`KompasDispatcher.InvokeAsync`); КОМПАС is single-instance STA. Services never touch COM off that thread. -- Connection goes through **API5** (`KOMPAS.Application.5` → `KompasObject`) then `ksGetApplication7()` → `IApplication`; the API5 root is needed for `ksPart` 3D-building and `ksDocument3D` snapshots. -- 3D is built via **API5 `ksPart`** (`NewEntity(o3d_sketch/o3d_bossExtrusion/o3d_bossRotated/o3d_fillet/o3d_chamfer/o3d_shellOperation=43/o3d_ribOperation=44/o3d_baseEvolution=45/...)`, `ksBossExtrusionDefinition`/`ksCutExtrusionDefinition`/`ksBossRotatedDefinition`/`ksFilletDefinition`/`ksChamferDefinition`/`ksShellDefinition`/`ksRibDefinition`/`ksBaseEvolutionDefinition`); cut-through holes use `dtBoth` + `etThroughAll`. API7 is used for app/documents. -- **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/`. 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`, `assembly_add_mate` (both API7, `AssemblyService`; mate types `coincidence`/`distance` verified), `drawing_create_standard_views` (API7, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`), `drawing_fill_title_block` (API7, `DrawingService`; заполнение основной надписи/штампа через `IStamp`), `drawing_add_linear_dimension` (API7, `DrawingService`; линейный размер на виде через `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`; координаты в локальной СК вида), `drawing_add_diametral_dimension` (API7, `DrawingService`; диаметральный размер через `ISymbols2DContainer.DiametralDimensions.Add`/`IDiametralDimension`; `Xc/Yc/Radius` в локальной СК вида, `Angle` в радианах, `NominalValue`=2·Radius), `drawing_add_radial_dimension` (API7, `DrawingService`; радиальный размер R через `ISymbols2DContainer.RadialDimensions.Add`/`IRadialDimension`; `Xc/Yc/Radius`+`Angle` (рад), `DimensionType=true`; **`NominalValue`=Radius, НЕ диаметр** — вопреки справке SDK, подтверждено спайком), `drawing_add_angular_dimension` (API7, `DrawingService`; угловой размер через `ISymbols2DContainer.AngleDimensions.Add(ksDrADimension=10)`/`IAngleDimension`; вершина `Xc/Yc` + точки сторон `X1/Y1`,`X2/Y2` + положение дуги `X3/Y3` (локальная СК вида); измеряемый угол выбирает `DimensionType`=`ksAngleDimTypeEnum` через enum `AngleDimensionType{Min,Max,More}`+`AngleDimensionTypes.Parse/ToKompas`; на лучах 0°/45° → min=45°/max=135°(супплемент)/more=315°(рефлексный); `NominalValue` в градусах; `(x3,y3)` лишь позиционирует дугу, угол НЕ выбирает), `drawing_add_rough` (API7, `DrawingService`; знак шероховатости через `ISymbols2DContainer.Roughs.Add()`/`IRough` (`BranchX0/Y0`,`Angle`) + `(IRoughParams)rough` QI: `SignType`=`ksRoughSignEnum` через enum `RoughSignType{NoProcessing,DeleteMaterial,WithoutDeleteMaterial}`+`RoughSignTypes.Parse/ToKompas`, значение Ra/Rz — `RoughParamText.Str`; возвращает значение read-back), `drawing_add_text` (API7, `DrawingService`; свободная надпись через `(IDrawingContainer)view.DrawingTexts.Add()`/`IDrawingText` (`X/Y/Angle`) + `(IText)dt.Str`=текст; **текст в `IDrawingContainer`, НЕ в `ISymbols2DContainer`, и НЕ в `IView.ObjectCount`** — счёт по `DrawingTexts.Count`; `Height` НЕ задаём — это высота блока, не шрифт), `drawing_set_technical_requirements` (API7, `DrawingService`; тех. требования уровня ДОКУМЕНТА через `(IDrawingDocument)doc.TechnicalDemand`/`ITechnicalDemand`: `td.Text.Str`=текст → `td.Update()`; `IsCreated` False→True при первом задании; перезаписывает; многострочно `\n`) are done.** `DrawingAnnotationResult{Value(read-back),ViewNumber}` — общий результат rough/text. Базовое семейство размеров 2D-чертежа завершено (линейный, диаметральный, радиальный, угловой); текстовые обозначения (шероховатость, надпись, тех. требования) завершены. 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-чертёж: ассоциативная привязка размеров/шероховатости к геометрии (`BaseObject`/`BaseObject1/2`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`) — далее; 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`. -- **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). -- **Rib operation (`rib`, package B)**: `ksRibDefinition` via `ksPart.NewEntity(o3d_ribOperation=44)`. Params: `sketchId`, `thickness` (mm), `side` (`left|right|up|down` → 0/1/2/3 via `SketchGeometry.RibSide()`), `symmetric` (bool), `angle` (°). `SetSketch(sketch)` → set `index=0`, `angle`, `side`; `SetThinParam(dtBoth, t/2, t/2)` when `symmetric=true`, else `SetThinParam(dtNormal, t, 0)`. Empirical: the rib contour must float in the gap (not touch the body at endpoints) — КОМПАС extends the web to the body automatically. -- **Sweep operation (`sweep`, package B)**: `ksBaseEvolutionDefinition` via `ksPart.NewEntity(o3d_baseEvolution=45)`. Params: `profileSketchId` (closed profile sketch), `pathSketchId` (open/closed path sketch on a different plane, typically perpendicular). `SetSketch(profile)` → `PathPartArray().Add(path)` → `sketchShiftType=0` → `SetThinParam(false)` → `Create()`. Validation: `profileSketchId != pathSketchId`. Profile and path must be on different (usually perpendicular) planes. -- **Offset plane (`sketch_create_on_offset_plane`, package E start)**: `OpenSketchOnOffsetPlaneAsync` in `PartModeler.Sketch.cs`. `ksPart.NewEntity(o3d_planeOffset=14)` → `ksPlaneOffsetDefinition` (`SetPlane(basePlane)` + `offset` mm + `direction`) → `Create()` → sketch on that plane. Used to place sketches at arbitrary heights — enables `loft` with parallel sections. `entity.Create()` result is checked (reliability fix applied to all `CreateSketchOn*` paths). -- **Loft operation (`loft`, package B)**: `ksBaseLoftDefinition` via `ksPart.NewEntity(o3d_baseLoft=30)`. Params: `sketchIds[]` (≥2 closed profile sketches on different parallel planes). `Sketchs().Add(each sketch)` → `SetLoftParam(closed=false, false, true)` → `SetThinParam(false)` → `Create()`. Requires parallel section sketches at different heights — use `sketch_create_on_offset_plane` for the non-base sections. -- **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`, `00`). Scope: `coincidence`/`distance` verified live; `parallel`/`perpendicular`/`concentric`/`angle`/`tangency` — future increments. DI: `AddSingleton()` in `Program.cs`. -- **Drawing service (`drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension`, `drawing_add_diametral_dimension`; `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)**: entry via API7 `IKompasDocument2D` — requires an active drawing document (`DocumentType==ksDocumentDrawing`). Standard views: `doc2d.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", projTypes, x, y, scale, dx=20, dy=20)` where `projTypes=object[]{1,3,5}` (ksPtFront/ksPtUp/ksPtLeft passed as SAFEARRAY VT_I4). Success check: `created==3` strictly; content check: sum of `IView.ObjectCount` for newly created views (identified by `IView.Number` delta) must be `>0`, otherwise error + rollback via `IView.Delete`. No rebuild needed — `ObjectCount>0` immediately on a new drawing. Validation: `DrawingValidation.NormalizeModelPath` (accepts .m3d/.a3d), `RequireScale>0`. Model file must be saved (`document_save_as`) before calling. Returns `DrawingViewsResult{Created,Total,ViewNumbers}` — `ViewNumbers` contains the IView.Number values of the three created views (used to address views when placing dimensions). Confirmed live: `Views.Count` 1→4 (system view + 3 projections). **Title block / stamp** (`drawing_fill_title_block`): `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (indexed property, `IText.Str` replaces content — no `Clear` needed, overwrite confirmed) → `stamp.Update()`. Own enum `StampField {Designation, Name, Material}` + static `StampFields.ColumnId` (→ 2/1/3, `ksStampEnum`) and `Collect` (gathers non-empty trimmed fields). `RequireStampCell` validates null + `columnId>0`. `RequireActiveDrawing` shared with views check. Write is non-transactional (all cells → single `Update`). New file `src/Kompas.Mcp.Core/Drawings/StampField.cs`; tool added to existing `DrawingTools.cs`. **Linear dimension** (`drawing_add_linear_dimension`): places a linear dimension on a view of the active drawing. API7 path: locate view by `viewNumber` index → COM-QI `(ISymbols2DContainer)view` (dimension goes into that specific view, NOT to the sheet) → `LineDimensions.Add()` → `ILineDimension`: set `X1,Y1,X2,Y2` (extension point coordinates in view-local CS, mm), `X3,Y3` (dimension line position), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true` → `Update()` → check `Valid` → read value via `((IDimensionText)dim).NominalValue`. Coordinates are in **view-local coordinate system** (not sheet). Parallel orientation works without explicit Angle (auto from points). Rollback: `dim.Delete()` if `Update=FALSE` / `Valid=false` / zero NominalValue (orientation incompatible with points). **Key finding: dimensions are NOT counted in `IView.ObjectCount`** (which counts geometry in `IDrawingContainer`); dimensions live in `ISymbols2DContainer` separately — verify placement via `ISymbols2DContainer.LineDimensions.Count`. Own enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Validators `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. New files: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; tool in existing `DrawingTools.cs`. **Diametral dimension** (`drawing_add_diametral_dimension`): places a diameter dimension (Ø) for a circle on a view. API7 path: shared helper `RequireSymbols2DContainer(viewNumber)` (reused by both linear and diametral paths) → `DiametralDimensions.Add()` → `IDiametralDimension`: `Xc`, `Yc`, `Radius` (all in view-local CS, mm), `Angle` (leader direction in **radians** — converted from degrees via `DimensionAngles.ToRadians`, with overflow check), `AutoNominalValue=true` → `Update()` → check `Valid` → `((IDimensionText)dim).NominalValue` == diameter (2·Radius). Dimension is "free" (no `BaseObject` — associative binding deferred). `DimensionType` not set (COM default). Validator `DrawingValidation.RequirePositiveRadius`. Invariant: returned value > 0. New helper `DimensionAngles.ToRadians` in `DimensionOrientation.cs`; `RequirePositiveRadius` in `DrawingValidation.cs`; tool in existing `DrawingTools.cs`. 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`. +- **Connection**: API5 (`KOMPAS.Application.5` → `KompasObject`) then `ksGetApplication7()` → `IApplication`; the API5 root is needed for `ksPart` 3D-building and `ksDocument3D` snapshots. API7 is used for app/documents, holes, assemblies, drawings, direct B-rep edit. +- **3D via API5 `ksPart`**: `NewEntity(o3d_*)` + matching definition; always check `entity.Create()` return. Entity codes: `o3d_sketch`/`o3d_bossExtrusion`/`o3d_cutExtrusion`/`o3d_bossRotated`/`o3d_fillet`/`o3d_chamfer`, `o3d_shellOperation=43`, `o3d_ribOperation=44`, `o3d_baseEvolution=45` (sweep), `o3d_baseLoft=30`, `o3d_planeOffset=14`, `o3d_meshCopy=35` (linear pattern), `o3d_circularCopy=36`, `o3d_mirrorOperation=48`, `o3d_mirrorAllOperation=49`, `o3d_incline=42` (draft), axes `o3d_axisOX/OY/OZ=71/72/73`. Definitions: `ksBossExtrusionDefinition`/`ksCutExtrusionDefinition`/`ksBossRotatedDefinition`/`ksFilletDefinition`/`ksChamferDefinition`/`ksShellDefinition`/`ksRibDefinition`/`ksBaseEvolutionDefinition`/`ksBaseLoftDefinition`/`ksPlaneOffsetDefinition`/`ksMeshCopyDefinition`/`ksCircularCopyDefinition`/`ksMirrorCopyDefinition`/`ksMirrorCopyAllDefinition`/`ksInclineDefinition`. Cut-through holes use `dtBoth` + `etThroughAll`. +- **Face/edge selection**: `ksPart.EntityCollection(o3d_face|o3d_edge)` → `SelectByPoint(x,y,z)` (world mm) → `GetByIndex(0)`; or by stable index from `list_faces`/`list_edges` (helpers `SelectFaceByIndex`/`SelectEdgeByIndex`, range-checked before mutation; by-index more reliable than by-point). Classify: `ksFaceDefinition.IsPlanar/IsCylinder/…` + `GetArea(ST_MIX_MM)`; `ksEdgeDefinition.IsLineSeg/IsCircle/IsArc/…` + `GetLength()`. Sketch axis = line with system style **3** (осевая). +- **МЦХ / 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** (structural «passport» — bbox + МЦХ + bodies + topology + feature tree + variables, no image token cost). +- 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`) → `DocumentService.CloseAllAsync` after each test (prevents tab accumulation). +- **Feature tree API**: 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()` + the matching definition (`ksBossExtrusionDefinition.GetSideParam`, `ksFilletDefinition.radius`, `ksChamferDefinition.GetChamferParam`, …). Node names ("Элемент выдавливания:1") are localized by КОМПАС. Features registered by id in `_features` support patterns/mirror; helpers `RequireSketch`/`RequireFeature`. +- **Variables (API5)**: create/read via `ksPart.GetFeature().VariableCollection` (property on root ksFeature) — **NOT** `ksPart.VariableCollection()` (returns external-only). Expression is the leading field; value is derived. Apply via `ksPart.RebuildModel()` (check return); after rebuild the RCW variable is stale → re-read via a fresh collection (`ReadValueFresh`). Dependents recalculate automatically; deletion fails if dependents exist (`RemoveVariable` returns FALSE). **Geometry limitation:** variables drive geometry only in a parametric model (sketch dims linked to variable names); our sketches use literal coords → `set_variable` stores/computes the value but does NOT move geometry. Parametric sketches **unavailable via COM** (`ksCDimWithVariable` not constructible from external automation — see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`; do not retry without new info). +- **Topology / measure API**: `ksFaceDefinition.EdgeCollection` / `GetCylinderParam` / `GetSurface().GetNormal` + `normalOrientation`; `ksEdgeDefinition.GetAdjacentFace(bool)` / `GetVertex(bool)` → `ksVertexDefinition.GetPoint`; bodies via `ksPart.BodyCollection()` → `ksBody` (`IsSolid` / `FaceCollection`); measurements via `ksPart.GetMeasurer()` → `ksMeasurer` (`SetObject1/2`, `unit=ST_MIX_MM`, `Calc`, `distance`/`MinDistance`/`angle` in degrees / `IsAngleValid`). +- **"STEP import without history"** detected structurally: `bodyCount > 0 && no formative features && features.Count <= bodyCount+1` (only origin + body). STEP with edits ("Смещённая плоскость" / "Разрезать" / "Переместить грани" / "Булева операция") already has history. +- **STEP import/export COM pattern** (verified): `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (pass format code, not DLL path) → `IConverter.ConverterParameters(cmd)` → `IAdditionConvertParameters.Format=ksConverterFromSTEP` → `IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`. Export: `ConvertToAdditionFormat` (format codes AP203/AP214/AP242). Param co-classes (AdditionConvertParameters etc.) are NOT CoCreatable — only via converter factories. Assembly traversal (`list_components`): `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): API7 face via `IPart7.FindObjectsByPoint(x,y,z,true)` → cast to `KompasAPI7.IFace`; containers via runtime COM-QI `(ISurfaceContainer)part`, `(IModelContainer)part`; `FaceMover` (SetFaces + Offset + Direction + Update) moves the face. distance>0 = outward (add material), <0 = inward. Works on parametric and imported B-rep. Full split-reposition workflow (SplitSolids/BodyRepositions) is driveable; only partly exposed as tools (`split_solid_by_plane`, `move_body`, `boolean_union`). +- **Forming ops (API5 definitions)**: + - `shell` (`ksShellDefinition`, o3d_shellOperation=43): `thickness` + `thinType` (bool, `= !outward`) + `FaceArray()` (open faces ≥1 by index). Transient RCWs intentionally not released (consistent with Fillet/Chamfer). + - `rib` (`ksRibDefinition`, =44): `SetSketch(sketch)` → `index=0`, `angle`, `side` (`left|right|up|down` → 0/1/2/3 via `SketchGeometry.RibSide()`); `SetThinParam(dtBoth, t/2, t/2)` if symmetric, else `(dtNormal, t, 0)`. Empirical: the rib contour must float in the gap (not touch the body) — КОМПАС extends the web automatically. + - `sweep` (`ksBaseEvolutionDefinition`, =45): `SetSketch(profile)` → `PathPartArray().Add(path)` → `sketchShiftType=0` → `SetThinParam(false)` → `Create()`. Profile & path on different (usually ⟂) planes; `profileSketchId != pathSketchId`. + - `loft` (`ksBaseLoftDefinition`, =30): `Sketchs().Add(each)` → `SetLoftParam(false,false,true)` → `SetThinParam(false)`. ≥2 closed sections on parallel planes — use `sketch_create_on_offset_plane` for non-base sections. + - offset plane (`sketch_create_on_offset_plane`, `ksPlaneOffsetDefinition`, o3d_planeOffset=14): `SetPlane(base)` + `offset` mm + `direction` → `Create()` → sketch on it (`OpenSketchOnOffsetPlaneAsync`). + - `draft` (`ksInclineDefinition`, o3d_incline=42 — API5 calls the op **Incline**, NOT Draft): `FaceArray()` (by index) + `SetPlane(neutral base plane)` + `angle` (degrees) + `direction`. **Empirical, opposite to SDK docs:** `direction=false`=expanding/outward (adds material), `true`=tapering/inward → code maps `def.direction = !outward`. Validate `0 configure)` — `configure` sets `HoleParameters` **after** `HoleType` is assigned (the type-specific sub-interface is inaccessible until the type is fixed). Placement shared: `IModelContainer`/`IHole3D`/`IHoleDisposal` + `Points3D`, direction chosen by volume delta, orphan rollback. None registered in `_features` → no id returned. `hole`=`ksHTBase`, configure=null. `hole_counterbore`=`ksHTCounterbore` + `(ISpotfacingHoleParameters)` (`SpotfacingDiameter`/`SpotfacingDepth`). `hole_countersink`=`ksHTCountersinking` + `(ICountersinkHoleParameters)` (`CountersinkType=ksCTDiameterAngle`, `CountersinkDiameter`/`Angle`). `hole_conic`=`ksHTConic` + `(IConicHoleParameters)` (`ConicType=ksCNAngle`, `ConicAngle`). +- **Assembly (API7, `AssemblyService`, namespace `Kompas.Mcp.Core.Assemblies`)**: `assembly_add_component` — `IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)` → `IPart7.Placement` (`IPlacement3D`) → `SetOrigin(x,y,z)` (mm, assembly world CS) → `UpdatePlacement(true)` → `top.RebuildModel(true)`. **Empirical:** `UpdatePlacement` returns FALSE for a manually-positioned component (no mates) — NOT an error. Validate `DocumentType==ksDocumentAssembly` before touching `TopPart`; rollback via `IFeature7.Delete` + `RebuildModel`. `assembly_add_mate` — `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)` → `BaseObject1/2` (faces via `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` (distance) → `Update()` → `RebuildModel(true)`; check `mate.Valid` (false → rollback `mate.Owner.Delete`). Types `coincidence`(mc_Coincidence=0) / `distance`(mc_Distance=5, value>0) verified live; `parallel`/`perpendicular`/`concentric`/`angle`/`tangency` — future. +- **Drawing (API7, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)** — entry via `IKompasDocument2D`; requires an active drawing doc (`DocumentType==ksDocumentDrawing`). All coords are **view-local** (mm) unless noted. Model file must be saved before placing views. + - `drawing_create_standard_views`: `doc2d.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", projTypes, x, y, scale, dx=20, dy=20)` where `projTypes=object[]{1,3,5}` (Front/Up/Left as SAFEARRAY VT_I4). Success: `created==3`; content check: sum of new views' `IView.ObjectCount` > 0 else rollback `IView.Delete`. Returns `ViewNumbers` (the `IView.Number` values — address views by these when placing annotations). + - `drawing_fill_title_block`: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (indexed property; `IText.Str` overwrites, no Clear needed) → `stamp.Update()`. Fields→columns (`ksStampEnum`): Name=1, Designation=2, Material=3. Non-transactional (all cells → single `Update`). + - **Dimensions go into `(ISymbols2DContainer)view`, NOT the sheet, and are NOT counted in `IView.ObjectCount`** (that counts geometry in `IDrawingContainer`) — verify placement via the container's `*.Count`. Common flow: `Add()` → set coords → `AutoNominalValue=true` → `Update()` → check `Valid` → read `((IDimensionText)dim).NominalValue`; rollback `dim.Delete()` on FALSE/invalid/zero. Leader/angle in **radians** (`DimensionAngles.ToRadians`). Free placement = set coords directly; **associative** (diametral/radial only) = set `BaseObject` to a projected circle → value read from geometry (see below). + - linear (`ILineDimension`): `X1,Y1,X2,Y2` (extension pts) + `X3,Y3` (dim line) + `Orientation` (`DimensionOrientation{Horizontal,Vertical,Parallel}` → `ksLinDParallel=0`/`Horizontal=1`/`Vertical=2`; parallel needs no explicit Angle). + - diametral (`IDiametralDimension`): `Xc,Yc,Radius` + `Angle`; `NominalValue` = 2·Radius. + - radial (`IRadialDimension`): `Xc,Yc,Radius` + `Angle`, `DimensionType=true`; **`NominalValue` = Radius, NOT diameter** — contrary to SDK docs, confirmed by spike. + - **associative** diametral/radial (`associate=true` flag): instead of coords, find a projected circle in `(IDrawingContainer)view.Circles` by center+radius key (`CircularObjectMatch.SelectMatchIndex`, tol 1 mm; throws on none/ambiguous; concentric disambiguated by radius), set `dim.BaseObject = circle` (`_Circle` implements `IDrawingObject`) → `NominalValue` read from geometry. `RequireViewContainers` gives both QIs; free path stays on `RequireSymbols2DContainer`. Arc/angular/rough binding — not yet (own spike needed; `ILineDimension` has no `BaseObject`). + - angular (`IAngleDimension`, `AngleDimensions.Add(ksDrADimension=10)`): vertex `Xc,Yc` + side pts `X1,Y1`/`X2,Y2` + arc-position `X3,Y3` (positions the arc only, does NOT pick the angle); measured angle chosen by `DimensionType` = `AngleDimensionType{Min,Max,More}` (on rays 0°/45° → min=45°/max=135° supplement/more=315° reflex). `NominalValue` in degrees. + - `drawing_add_rough` (`Roughs.Add()`/`IRough`: `BranchX0/Y0`, `Angle`) + `(IRoughParams)rough` QI: `SignType` = `RoughSignType{NoProcessing,DeleteMaterial,WithoutDeleteMaterial}`, value (Ra/Rz) = `RoughParamText.Str`. + - `drawing_add_text` — **text lives in `IDrawingContainer`, NOT `ISymbols2DContainer`, and NOT in `IView.ObjectCount`**: `(IDrawingContainer)view.DrawingTexts.Add()`/`IDrawingText` (`X/Y/Angle`) + `(IText)dt.Str` = text; count via `DrawingTexts.Count`. Do NOT set `Height` (that's block height, not font). + - `drawing_set_technical_requirements` — **document-level**: `(IDrawingDocument)doc.TechnicalDemand`/`ITechnicalDemand`: `td.Text.Str = text` → `td.Update()` (`IsCreated` False→True on first set; overwrites; `\n` multiline). + - `DrawingAnnotationResult{Value(read-back), ViewNumber}` is the shared result for rough/text. +- **Richer sketch primitives (API5 `ksDocument2D` wrappers)**: `ksArcBy3Points`; `ksArcByAngle` (centre/radius/start-end angles in degrees + counterClockwise flag); `ksEllipse` via `ksEllipseParam` (`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 released after use. `PartModeler` is partial: `.cs` (registry/helpers/`NewParam`/reset), `.Sketch.cs` (2D primitives), `.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` (`Core/Modeling`) holds enum maps + validators (`RequirePositive`, `RequireVertexCount`, `RequirePoints`, `RequireMin`). Point lists use `record SketchPoint(X, Y)` (JSON names `x`/`y`). ## КОМПАС-3D API architecture (the critical context) From ff5fb27dd5a6cd14dec90b8fc8f07b878b2976f8 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 21:57:45 +0300 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20=D0=B0=D1=81=D1=81=D0=BE=D1=86?= =?UTF-8?q?=D0=B8=D0=B0=D1=82=D0=B8=D0=B2=D0=BD=D0=B0=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D0=B0=20=D0=B4=D0=B8=D0=B0=D0=BC?= =?UTF-8?q?./=D1=80=D0=B0=D0=B4=D0=B8=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=E2=80=94=20TODO,=20README,=20ARCHITECTURE,=20OPEN=5FQ?= =?UTF-8?q?UESTIONS,=20presentation,=20CLAUDE.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Инкремент 7. 81 инструмент (associate — флаг существующих), 278 тестов. CLAUDE.md также включает структурную чистку (делегирование каталога/дорожной карты/ бэклога в README/ARCHITECTURE/TODO без дублирования). Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- docs/ARCHITECTURE.md | 12 ++++++------ docs/OPEN_QUESTIONS.md | 5 +++++ docs/TODO.md | 13 +++++++++---- docs/presentation.html | 13 +++++++------ 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 306aeb0..ab5d9d7 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,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/ 267 тестов: unit + integration (integration требуют КОМПАС) +tests/Kompas.Mcp.Tests/ 278 тестов: 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 88639b0..de153da 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`+`assembly_add_mate`+`drawing_create_standard_views`+`drawing_fill_title_block`+`drawing_add_linear_dimension`+`drawing_add_diametral_dimension`+`drawing_add_radial_dimension`+`drawing_add_angular_dimension`+`drawing_add_rough`+`drawing_add_text`+`drawing_set_technical_requirements` (веха «2D-чертёж», инкремент 6 — текстовые обозначения)). Актуализировано по коду. +> Статус: **реализовано и работает** (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`+`drawing_create_standard_views`+`drawing_fill_title_block`+`drawing_add_linear_dimension`+`drawing_add_diametral_dimension`+`drawing_add_radial_dimension`+`drawing_add_angular_dimension`+`drawing_add_rough`+`drawing_add_text`+`drawing_set_technical_requirements`+ассоциативная привязка диаметрального/радиального к окружности (веха «2D-чертёж», инкремент 7)). Актуализировано по коду. > Сопутствующий контекст по 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`), `DrawingService` (стандартные виды чертежа, namespace `Kompas.Mcp.Core.Drawings`), снимок. Все паттерны API5/API7 живут здесь. | -| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 267 тестов (163 unit + 104 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | +| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 278 тестов (168 unit + 110 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики. @@ -212,8 +212,8 @@ MCP client ──stdio──> Host (поток-пул, async) - `drawing_create_standard_views(partFilePath, scale, x, y)` — создать стандартные ассоциативные виды (спереди/сверху/слева, `ksPtFront/ksPtUp/ksPtLeft`) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже. Реализовано через API7 `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, dx=20, dy=20)` — `object[]` передаётся как SAFEARRAY VT_I4. Проверки: `created==3` строго; непустота — сумма `IView.ObjectCount` новых видов (по разнице `IView.Number`) `>0`, иначе ошибка + откат (`IView.Delete`). Возвращает `DrawingViewsResult{Created,Total,ViewNumbers}` — `ViewNumbers` содержит номера созданных видов (для адресации при простановке размеров). Модель должна быть сохранена до вызова. - `drawing_fill_title_block(designation?, name?, material?)` — заполнить графы основной надписи (штампа) активного чертежа. `designation` = обозначение документа (графа 2), `name` = наименование изделия (графа 1), `material` = материал (графа 3); нужно хотя бы одно поле. Реализовано: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (индексированное свойство; `IText.Str` замещает содержимое без `Clear`) → `stamp.Update()`. Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`. Возвращает число заполненных граф. - `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — поставить линейный размер на виде активного чертежа. Координаты (x1,y1)-(x2,y2) — выносные точки в **локальной СК вида** (мм); (x3,y3) — положение размерной линии; `orientation`: `horizontal`|`vertical`|`parallel`; `viewNumber` — номер вида (0=первый). Значение измеряется автоматически (`AutoNominalValue=true`). Реализовано: `(ISymbols2DContainer)view` (COM-QI от конкретного `IView`, не от листа — размер попадает именно в этот вид) → `LineDimensions.Add()` → `ILineDimension` → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Ключевая находка: **размеры не входят в `IView.ObjectCount`** (он считает геометрию `IDrawingContainer`); проверять через `ISymbols2DContainer.LineDimensions.Count`. Откат через `dim.Delete()` при ошибке или нулевом значении. Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. -- `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber)` — поставить диаметральный размер (Ø) окружности на виде активного чертежа. `xc,yc` — центр, `radius` — радиус (мм, локальная СК вида); `angle` — направление выноски (градусы → радианы через `DimensionAngles.ToRadians`); `viewNumber` — номер вида (0=первый). Значение (диаметр = 2·radius) измеряется автоматически. Реализовано: общий хелпер `RequireSymbols2DContainer(viewNumber)` (переиспользуется с линейным размером) → `DiametralDimensions.Add()` → `IDiametralDimension` (`Xc`, `Yc`, `Radius`, `Angle` в радианах, `AutoNominalValue=true`) → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Размер «свободный» (без `BaseObject`). Валидатор `DrawingValidation.RequirePositiveRadius`. `DimensionAngles.ToRadians` добавлен в `DimensionOrientation.cs`. -- `drawing_add_radial_dimension(xc,yc,radius, angle, viewNumber)` — поставить радиальный размер (R) окружности/дуги на виде активного чертежа. `xc,yc` — центр, `radius` — радиус (мм, локальная СК вида); `angle` — направление выноски (°, по умолчанию 0); `viewNumber` — номер вида. Возвращаемое значение равно **радиусу** (не диаметру). Реализовано: `RequireSymbols2DContainer(viewNumber)` → `RadialDimensions.Add()` → `IRadialDimension` (`Xc`, `Yc`, `Radius`, `Angle` в радианах, `DimensionType=true`, `AutoNominalValue=true`) → `Update()` → `Valid` → `NominalValue`. Размер «свободный» (без `BaseObject`). Переиспользует `RequirePositiveRadius` и `DimensionAngles.ToRadians`. +- `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber, associate=false)` — поставить диаметральный размер (Ø) окружности на виде активного чертежа. `xc,yc` — центр, `radius` — радиус (мм, локальная СК вида); `angle` — направление выноски (градусы → радианы через `DimensionAngles.ToRadians`); `viewNumber` — номер вида (0=первый). `associate=false` — «свободный» размер без привязки (значение = 2·radius); `associate=true` — **ассоциативный режим**: `xc/yc/radius` служат ключом для поиска спроецированной окружности на виде (`IDrawingContainer.Circles`, поиск по центру+радиусу с допуском 1 мм) через `CircularObjectMatch`; найденная окружность присваивается `dim.BaseObject`, значение (Ø) читается из геометрии и обновляется вместе с моделью. Реализовано: `RequireSymbols2DContainer(viewNumber)` + `RequireViewContainers` → `DiametralDimensions.Add()` → `IDiametralDimension` → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Валидатор `DrawingValidation.RequirePositiveRadius`. +- `drawing_add_radial_dimension(xc,yc,radius, angle, viewNumber, associate=false)` — поставить радиальный размер (R) окружности/дуги на виде активного чертежа. `xc,yc` — центр, `radius` — радиус (мм, локальная СК вида); `angle` — направление выноски (°, по умолчанию 0); `viewNumber` — номер вида. Возвращаемое значение равно **радиусу** (не диаметру). `associate=false` — «свободный» размер (значение = radius); `associate=true` — **ассоциативный режим**: поиск окружности по `xc/yc/radius` в `IDrawingContainer.Circles` (допуск 1 мм) через `CircularObjectMatch`, привязка `dim.BaseObject`, значение из геометрии. Реализовано: `RequireSymbols2DContainer(viewNumber)` + `RequireViewContainers` → `RadialDimensions.Add()` → `IRadialDimension` (`Xc`, `Yc`, `Radius`, `Angle` в радианах, `DimensionType=true`, `AutoNominalValue=true`) → `Update()` → `Valid` → `NominalValue`. Переиспользует `RequirePositiveRadius` и `DimensionAngles.ToRadians`. - `drawing_add_angular_dimension(xc,yc, x1,y1, x2,y2, x3,y3, angleType, viewNumber)` — поставить угловой размер между двумя сторонами на виде активного чертежа. `xc,yc` — вершина угла; `x1,y1` — точка на стороне 1; `x2,y2` — точка на стороне 2; `x3,y3` — положение размерной дуги (задаёт её радиус); `angleType` = `"min"` (острый) | `"max"` (тупой, 180°-min) | `"more"` (рефлексный >180°); `viewNumber` — номер вида. Значение в градусах измеряется автоматически. Реализовано: `RequireSymbols2DContainer(viewNumber)` → `AngleDimensions.Add(ksDrADimension)` → `IAngleDimension` (`Xc`, `Yc`, `X1`, `Y1`, `X2`, `Y2`, `X3`, `Y3`, `DimensionType=ksAngleDimTypeEnum`, `AutoNominalValue=true`) → `Update()` → `Valid` → `NominalValue`. Новый enum `AngleDimensionType {Min, Max, More}` + `AngleDimensionTypes.Parse/ToKompas` в `DimensionOrientation.cs`; 9 unit-тестов для маппинга. Размер «свободный» (без `BaseObject`). Валидаторы `RequireFiniteCoords` + `RequireDistinctPoints`. - `drawing_add_rough(x,y, value?, signType, angle, viewNumber)` — поставить знак шероховатости на виде. `x,y` — положение знака в локальной СК вида (мм); `value` — текст параметра (напр. `"Ra 1.6"`; пусто = знак без значения); `signType`: `"delete"` (с удалением слоя материала) | `"without"` (без удаления) | `"none"` (без указания); `angle` — угол наклона оси знака (°, по умолч. 0); `viewNumber` — номер вида. Реализовано: `ISymbols2DContainer.Roughs.Add()` → `IRough` (`BranchX0/Y0/Angle`) → `(IRoughParams)rough` (`SignType=ksRoughSignEnum`, `RoughParamText.Str = value`) → `Update()` → `Valid`. Новый enum `RoughSignType {NoProcessing, DeleteMaterial, WithoutDeleteMaterial}` + `RoughSignTypes.Parse/ToKompas` в `Drawings/RoughSignType.cs`. Возвращает `DrawingAnnotationResult { Value, ViewNumber }`. - `drawing_add_text(x,y, text, angle, viewNumber)` — свободная текстовая надпись на виде стилем по умолчанию. `x,y` — точка привязки (СК вида, мм); `text` — содержимое (`\n` — многострочно); `angle` — угол (°, по умолч. 0); `viewNumber` — номер вида. Реализовано: `(IDrawingContainer)view.DrawingTexts.Add()` → `IDrawingText` (`X/Y/Angle`) → `(IText)dt.Str = text` → `Update()` → `Valid`. ВАЖНО: текст живёт в `IDrawingContainer` (контейнер геометрии вида), НЕ в `ISymbols2DContainer`; попадание проверяется через `DrawingTexts.Count`. Возвращает `DrawingAnnotationResult { Value, ViewNumber }`. @@ -352,14 +352,14 @@ 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+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension+drawing_add_diametral_dimension+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements):** документы, эскизы (полный набор 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`); **`drawing_create_standard_views`** (стандартные ассоциативные виды чертежа, API7, `DrawingService`); **`drawing_fill_title_block`** (заполнение основной надписи/штампа: обозначение, наименование, материал; `IStamp.Text[id].Str`, API7, `DrawingService`); **`drawing_add_linear_dimension`** (линейный размер на виде чертежа: `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`, координаты в локальной СК вида, API7, `DrawingService`); **`drawing_add_diametral_dimension`** (диаметральный размер Ø: `ISymbols2DContainer.DiametralDimensions.Add`/`IDiametralDimension`, Xc/Yc/Radius/Angle в радианах, API7, `DrawingService`); **`drawing_add_radial_dimension`** (радиальный размер R: `ISymbols2DContainer.RadialDimensions.Add`/`IRadialDimension`, значение = радиус, API7, `DrawingService`); **`drawing_add_angular_dimension`** (угловой размер: `ISymbols2DContainer.AngleDimensions.Add`/`IAngleDimension`, `angleType`=min/max/more, значение в градусах, API7, `DrawingService`); **`drawing_add_rough`** (знак шероховатости: `ISymbols2DContainer.Roughs.Add`/`IRough`/`IRoughParams`, `signType`=delete/without/none, API7, `DrawingService`); **`drawing_add_text`** (свободная текстовая надпись на виде: `IDrawingContainer.DrawingTexts.Add`/`IDrawingText`/`IText`, API7, `DrawingService`); **`drawing_set_technical_requirements`** (технические требования чертежа: `IDrawingDocument.TechnicalDemand`/`ITechnicalDemand`/`IText`, уровень документа, API7, `DrawingService`). Класс «2D-чертёж» (инкремент 6): стандартные виды, основная надпись, размеры (линейный, диаметральный, радиальный, угловой), текстовые обозначения (шероховатость, свободный текст, технические требования). +**Реализовано (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+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension+drawing_add_diametral_dimension+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements+associate для диаметрального/радиального):** документы, эскизы (полный набор 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`); **`drawing_create_standard_views`** (стандартные ассоциативные виды чертежа, API7, `DrawingService`); **`drawing_fill_title_block`** (заполнение основной надписи/штампа: обозначение, наименование, материал; `IStamp.Text[id].Str`, API7, `DrawingService`); **`drawing_add_linear_dimension`** (линейный размер на виде чертежа: `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`, координаты в локальной СК вида, API7, `DrawingService`); **`drawing_add_diametral_dimension`** (диаметральный размер Ø: `ISymbols2DContainer.DiametralDimensions.Add`/`IDiametralDimension`, Xc/Yc/Radius/Angle в радианах, API7, `DrawingService`; **`associate=true`** — ассоциативная привязка к спроецированной окружности через `dim.BaseObject`, `CircularObjectMatch`); **`drawing_add_radial_dimension`** (радиальный размер R: `ISymbols2DContainer.RadialDimensions.Add`/`IRadialDimension`, значение = радиус, API7, `DrawingService`; **`associate=true`** — ассоциативная привязка аналогично диаметральному); **`drawing_add_angular_dimension`** (угловой размер: `ISymbols2DContainer.AngleDimensions.Add`/`IAngleDimension`, `angleType`=min/max/more, значение в градусах, API7, `DrawingService`); **`drawing_add_rough`** (знак шероховатости: `ISymbols2DContainer.Roughs.Add`/`IRough`/`IRoughParams`, `signType`=delete/without/none, API7, `DrawingService`); **`drawing_add_text`** (свободная текстовая надпись на виде: `IDrawingContainer.DrawingTexts.Add`/`IDrawingText`/`IText`, API7, `DrawingService`); **`drawing_set_technical_requirements`** (технические требования чертежа: `IDrawingDocument.TechnicalDemand`/`ITechnicalDemand`/`IText`, уровень документа, API7, `DrawingService`). Класс «2D-чертёж» (инкремент 6): стандартные виды, основная надпись, размеры (линейный, диаметральный, радиальный, угловой), текстовые обозначения (шероховатость, свободный текст, технические требования). **Инкремент 7**: ассоциативная привязка диаметрального и радиального размеров к спроецированной окружности (`IDrawingContainer.Circles`, поиск по центру+радиусу с допуском 1 мм, `CircularObjectMatch`, `dim.BaseObject = circle`); первый шаг к «умному» чертежу. **Следующие приоритеты:** 1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D). ⚠️ **Исследовано**: `ksCDimWithVariable` недоступен из внешней автоматизации — см. `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`. 2. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу. 3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена. 4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`. -5. **2D-чертёж (продолжение):** инкремент 6 добавил текстовые обозначения — `drawing_add_rough` (шероховатость), `drawing_add_text` (свободный текст), `drawing_set_technical_requirements` (тех. требования). Нереализовано: ассоциативная привязка размеров и шероховатости к геометрии (`BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`). +5. **2D-чертёж (продолжение):** инкремент 7 добавил ассоциативную привязку диаметрального/радиального размера к окружности (`associate=true`). Нереализовано: привязка к дуге (`Arcs`), ассоциативный угловой размер (BaseObject1/2, отрезки), ассоциативная шероховатость (`IRough.BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`). 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 d13446a..f48e4da 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -220,3 +220,8 @@ stateful-сессии? Пока полагаемся на последовате `drawing_set_technical_requirements(text)` — технические требования документа (уровень документа, не вида). Паттерн API7: `(IDrawingDocument)doc.TechnicalDemand` → `td.Text.Str = text` → `td.Update()`. Перезаписывает прежние. Возвращает число строк. Итог: **81 инструмент, 267 тестов (163 unit + 104 integration).** Нереализовано: ассоциативная привязка размеров и шероховатости к геометрии (`BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`). + +### ✅ Ассоциативная привязка диаметрального/радиального к окружности — реализована (инкремент 7 вехи «2D-чертёж») +**Решено (частично).** Параметр `associate=true` добавлен к `drawing_add_diametral_dimension` и `drawing_add_radial_dimension`. Реализация (API7, `DrawingService`): новый класс `CircularObjectMatch` — отбирает окружность-кандидата в `IDrawingContainer.Circles` по центру (xc,yc) + радиусу с допуском 1 мм; бросает при отсутствии или неоднозначности (концентрические окружности различаются по радиусу). Хелпер `RequireViewContainers` получает одновременно `ISymbols2DContainer` и `IDrawingContainer` для вида. `_Circle` реализует `IDrawingObject`, поэтому `dim.BaseObject = circle` без дополнительного приведения. `associate=false` — прежнее «свободное» поведение без изменений. Подтверждено спайком: `BaseObject=circle` без задания Xc/Yc/Radius → `Valid`, `NominalValue` из геометрии (цилиндр R10: Ø=20 / R=10). +5 unit-тестов (`CircularObjectMatchTests`) + 6 интеграционных (`DrawingAssociativeDimensionTests`). Итог: **81 инструмент, 278 тестов (168 unit + 110 integration).** + +Нереализовано (следующие инкременты): привязка к дуге (`IDrawingContainer.Arcs`), ассоциативный угловой размер (`BaseObject1/2`, отрезки), ассоциативная шероховатость (`IRough.BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`). diff --git a/docs/TODO.md b/docs/TODO.md index 3f70749..c92bcd6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -13,10 +13,15 @@ Готово: стандартные виды, основная надпись (штамп), размеры (линейный/диаметральный/радиальный/угловой), текстовые обозначения (шероховатость, свободный текст, технические требования). -- ☐ **Ассоциативная привязка размеров и шероховатости к геометрии вида** (`BaseObject`/`BaseObject1/2`). - Обозначение «прилипает» к рёбрам/окружностям вида и обновляется с моделью, а не задаётся координатами. - Самый ценный следующий шаг («умный» чертёж). Требует спайка: перечисление геометрии вида - (`IDrawingContainer`: LineSegments/Circles/Arcs), выбор объекта по точке/индексу, связывание. +- ▣ **Ассоциативная привязка размеров и шероховатости к геометрии вида** (`BaseObject`/`BaseObject1/2`). + Обозначение «прилипает» к геометрии вида и обновляется с моделью, а не задаётся координатами. + - ✓ Диаметральный/радиальный → **окружность** (`Circles`): флаг `associate` (инкремент 7); поиск по + центру+радиусу (`CircularObjectMatch`), значение Ø/R из геометрии. + - ☐ Привязка к **дуге** (`Arcs`) — нужен свой спайк (`IArc` как `BaseObject`; дуги одного родителя + неотличимы по центру+радиусу — нужен ключ по углу/точке). + - ☐ **Угловой** → пара отрезков (`BaseObject1/2`, `LineSegments`). + - ☐ **Шероховатость** → `IRough.BaseObject` (привязка к контуру/ребру). + - ⛔/☐ **Линейный** — у `ILineDimension` нет `BaseObject`; нужен иной механизм (отложено). - ☐ **Рамка/формат листа** (A4/A3…, ориентация) — `ILayoutSheet`/`SheetFormat`. Быстрый фундаментальный шаг, вероятно без спайка. Делает чертёж «правильным» по ГОСТ. - ☐ **Выноски** (`Leaders`) — `ISymbols2DContainer.Leaders` (полки-выноски с текстом). diff --git a/docs/presentation.html b/docs/presentation.html index f83f8d3..44651ba 100644 --- a/docs/presentation.html +++ b/docs/presentation.html @@ -128,7 +128,7 @@
- Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension+drawing_add_diametral_dimension+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements (81 инструмент) + Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension+drawing_add_diametral_dimension+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements+associate (81 инструмент)

КОМПАС-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 · assembly_add_mate · drawing_create_standard_views · drawing_fill_title_block · drawing_add_linear_dimension · drawing_add_diametral_dimension · drawing_add_radial_dimension · drawing_add_angular_dimension · drawing_add_rough · drawing_add_text · drawing_set_technical_requirements · навык kompas-3d · 81 инструмент · 267 тестов
+
Общий прогресс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 · drawing_create_standard_views · drawing_fill_title_block · drawing_add_linear_dimension · drawing_add_diametral_dimension · drawing_add_radial_dimension · drawing_add_angular_dimension · drawing_add_rough · drawing_add_text · drawing_set_technical_requirements · associate (ассоциативная привязка диам./радиального) · навык kompas-3d · 81 инструмент · 278 тестов
@@ -310,8 +310,8 @@
  • Variables: create_variable · set_variable · delete_variable (пакет D)
  • Conversion: import_step · export_step
  • Assembly: assembly_add_component (вставка компонента .m3d/.a3d в сборку, API7) · assembly_add_mate (сопряжения coincidence/distance, API7)
  • -
  • Drawing: drawing_create_standard_views (стандартные ассоциативные виды чертежа, API7) · drawing_fill_title_block (основная надпись/штамп: обозначение, наименование, материал, API7) · drawing_add_linear_dimension (линейный размер на виде, ISymbols2DContainer, API7) · drawing_add_diametral_dimension (диаметральный размер Ø, IDiametralDimension, API7) · drawing_add_radial_dimension (радиальный размер R, IRadialDimension, API7) · drawing_add_angular_dimension (угловой размер, IAngleDimension, angleType=min/max/more, API7) · drawing_add_rough (знак шероховатости, IRough/IRoughParams, signType=delete/without/none, API7) · drawing_add_text (свободный текст на виде, IDrawingContainer/IDrawingText, API7) · drawing_set_technical_requirements (тех. требования документа, IDrawingDocument/TechnicalDemand, API7)
  • -
  • 2D-чертёж: ассоциативная привязка размеров и шероховатости к геометрии (BaseObject), рамка/формат, выноски (Leaders), обозначения баз (Bases), допуски формы (Tolerances) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
  • +
  • Drawing: drawing_create_standard_views (стандартные ассоциативные виды чертежа, API7) · drawing_fill_title_block (основная надпись/штамп: обозначение, наименование, материал, API7) · drawing_add_linear_dimension (линейный размер на виде, ISymbols2DContainer, API7) · drawing_add_diametral_dimension (диаметральный размер Ø, IDiametralDimension, associate=true для ассоциативной привязки к окружности, API7) · drawing_add_radial_dimension (радиальный размер R, IRadialDimension, associate=true аналогично, API7) · drawing_add_angular_dimension (угловой размер, IAngleDimension, angleType=min/max/more, API7) · drawing_add_rough (знак шероховатости, IRough/IRoughParams, signType=delete/without/none, API7) · drawing_add_text (свободный текст на виде, IDrawingContainer/IDrawingText, API7) · drawing_set_technical_requirements (тех. требования документа, IDrawingDocument/TechnicalDemand, API7)
  • +
  • 2D-чертёж: привязка к дуге (Arcs), ассоциативный угловой размер, ассоциативная шероховатость (IRough.BaseObject), рамка/формат, выноски (Leaders), обозначения баз (Bases), допуски формы (Tolerances) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
  • @@ -322,7 +322,7 @@
    • Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText
    • Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция
    • -
    • Итого 267 тестов зелёных (163 unit + 104 integration)
    • +
    • Итого 278 тестов зелёных (168 unit + 110 integration)
    • Сборка kompas-mcp.exe, README с конфигом клиента
    • Навык kompas-3d (методика) + полигон usecases/
    • In-process тест MCP-клиента, релизный publish
    • @@ -356,7 +356,8 @@
      2D-чертёж · инкремент 4 ✓

      Диаметральный размер

      drawing_add_diametral_dimension (ISymbols2DContainer.DiametralDimensions.Add/IDiametralDimension, Xc/Yc/Radius/Angle рад, API7) · RequireSymbols2DContainer общий хелпер · DimensionAngles.ToRadians · 76 инструментов · 218 тестов

      2D-чертёж · инкремент 5 ✓

      Радиальный и угловой размеры

      drawing_add_radial_dimension (IRadialDimension, DimensionType=true, API7) · drawing_add_angular_dimension (IAngleDimension, angleType=min/max/more, AngleDimensionType enum, API7) · базовое семейство размеров завершено · 78 инструментов · 241 тест

      2D-чертёж · инкремент 6 ✓

      Текстовые обозначения

      drawing_add_rough (IRough/IRoughParams, signType=delete/without/none, API7) · drawing_add_text (IDrawingContainer/IDrawingText/IText, API7) · drawing_set_technical_requirements (IDrawingDocument/TechnicalDemand, уровень документа, API7) · DrawingAnnotationResult · RoughSignType enum · 81 инструмент · 267 тестов

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

      Оформление чертежа

      Ассоциативная привязка размеров и шероховатости к геометрии (BaseObject) · рамка/формат · выноски (Leaders) · обозначения баз (Bases) · допуски формы (Tolerances) · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API

      +
      2D-чертёж · инкремент 7 ✓

      Ассоциативная привязка к окружности

      associate=true у drawing_add_diametral_dimension и drawing_add_radial_dimension · CircularObjectMatch (поиск по центру+радиусу, допуск 1 мм) · RequireViewContainers · dim.BaseObject=circle · значение из геометрии · 81 инструмент · 278 тестов

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

      Оформление чертежа

      Привязка к дуге (Arcs) · ассоциативный угловой размер · ассоциативная шероховатость (IRough.BaseObject) · рамка/формат · выноски (Leaders) · обозначения баз (Bases) · допуски формы (Tolerances) · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API