From 246233727c3b249708ef385f5b16f35c6edc4d03 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 19:51:10 +0300 Subject: [PATCH 1/3] =?UTF-8?q?feat(drawing):=20=D1=80=D0=B0=D0=B4=D0=B8?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20(R)=20=D0=B8=20=D1=83?= =?UTF-8?q?=D0=B3=D0=BB=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=80=D0=B0=D0=B7=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20=D0=BD=D0=B0=20=D0=B2=D0=B8=D0=B4=D0=B5=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D1=82=D0=B5=D0=B6=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drawing_add_radial_dimension (R окружности/дуги; NominalValue = радиус) и drawing_add_angular_dimension (угол между двумя сторонами; angleType min/max/more выбирает измеряемый сектор) через API7 ISymbols2DContainer. Завершают базовое семейство размеров чертежа (после линейного и диаметрального). - DrawingService: AddRadialDimensionAsync/AddAngularDimensionAsync + счётчики - AngleDimensionType (enum + Parse/ToKompas, по образцу DimensionOrientations) - DrawingDimensionResult.Value — единице-нейтральный (мм / градусы) - 9 unit + 13 интеграционных тестов (всего 240 зелёных) Спайк подтвердил: радиальный NominalValue = РАДИУС (не диаметр, вопреки справке); у углового измеряемый угол выбирает angleType (min=90°/more=270°), а (x3,y3) лишь позиционирует дугу. Спек и ревью Codex — docs/superpowers/specs/. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...drawing-radial-angular-dimension-design.md | 165 ++++++++++++++++ .../Drawings/AngleDimensionType.cs | 42 +++++ .../Drawings/DrawingDimensionResult.cs | 2 +- .../Drawings/DrawingService.cs | 103 ++++++++++ src/Kompas.Mcp.Host/Tools/DrawingTools.cs | 44 +++++ .../AngleDimensionTypesTests.cs | 30 +++ .../Integration/DrawingAngularTests.cs | 178 ++++++++++++++++++ .../Integration/DrawingRadialTests.cs | 115 +++++++++++ 8 files changed, 678 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md create mode 100644 src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs create mode 100644 tests/Kompas.Mcp.Tests/AngleDimensionTypesTests.cs create mode 100644 tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs create mode 100644 tests/Kompas.Mcp.Tests/Integration/DrawingRadialTests.cs diff --git a/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md b/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md new file mode 100644 index 0000000..35a1331 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md @@ -0,0 +1,165 @@ +# Дизайн: радиальный (R) и угловой размеры на виде чертежа через API7 + +**Дата:** 2026-05-27 +**Статус:** реализовано и проверено (5 радиальных + 8 угловых интеграционных + 9 unit; всего 240 тестов зелёных); ревью Codex спека учтено + +## Уточнение по реализации (важно) + +Спайк изначально решил, что измеряемый сектор выбирает положение дуги `(x3,y3)`. **При реализации +выяснилось иначе:** сектор (90° vs 270°) выбирает **`angleType`** (`ksADMinAngle`/`MaxAngle`/`MoreAngle`), +а `(x3,y3)` лишь позиционирует размерную дугу и задаёт её радиус. (270° в спайке возникал потому, что +`X3/Y3` не были заданы = совпадали с вершиной → вырождение.) Проверено: `angleType=Min`→90°, +`angleType=More`→270° на одной геометрии. Тест #8 и описания инструмента/сервиса исправлены под это. + +## Правки по ревью Codex (спек) + +- **#1** угловой: добавлен `RequireDistinctPoints(x1,y1,x2,y2)` (совпавшие точки сторон → одинаковые + лучи → вырождение ещё до `Add`). +- **#3** добавлен тест отката после `Add`: коллинеарные сонаправленные лучи (вершина(0,0),(10,0),(20,0)) + → угол ≈0 → пост-проверка `value<=0` → откат `Delete` + ошибка; счётчик угловых не изменился. +- **#2** поведение `x3,y3` (выбор сектора) задокументировано в описании инструмента; добавлен + интеграционный тест выбора сектора (та же геометрия, `x3,y3` в дополнительном секторе → ~270°). + Большой/тупой угол НЕ отвергаем — это легитимный результат (рефлексный угол через `more`/положение дуги). +- **#4** добавлены тесты на несуществующий `viewNumber>0` (ветка `FindView` «вид не найден») для обоих. +- **#5** `DrawingDimensionResult.Value` — XML-док станет единице-нейтральным («значение размера»); + ответ `drawing_add_angular_dimension` форматируется в градусах (°), не «мм». +- **#6** радиальный использует `DimensionAngles.ToRadians(angleDeg)` (как диаметральный), не ручное `·π/180`. + +## Цель + +Веха 2D-ЧЕРТЁЖ, инкремент 5. После линейного и диаметрального размеров — **радиальный (R)** и +**угловой** размеры на виде. Завершают базовое семейство размеров чертежа. Оба переиспользуют +проверенный путь `ISymbols2DContainer` (QI от `IView`) и паттерн «Add → set → Update → проверка +`Valid` → чтение `NominalValue` → откат `Delete` при ошибке». + +## Спайк: поведение подтверждено вживую (НЕ разучивать) + +Спайк (`_SpikeDimensions`, прогнан на реальном КОМПАС, затем удалён) на коробке 40×30×20 с +тремя стандартными видами. Контейнер размеров — `(ISymbols2DContainer)view`. + +### Радиальный + +``` +IRadialDimension rd = symbols.RadialDimensions.Add(); +rd.Xc = 10; rd.Yc = 10; rd.Radius = 15; rd.Angle = π/4; // Angle — РАДИАНЫ (направление выноски) +rd.DimensionType = true; // BOOL: на значение НЕ влияет (стиль) +((IDimensionText)rd).AutoNominalValue = true; +rd.Update(); // True, rd.Valid == True +double value = ((IDimensionText)rd).NominalValue; // == 15 — РАДИУС (не диаметр!) +``` + +**Ключевой факт (расходится со справкой SDK):** у радиального `NominalValue` возвращает **радиус** +(`Radius=15 → NominalValue=15`), а НЕ диаметр. Проверено для `DimensionType` и `true`, и `false` — +оба `Valid`, значение одинаковое (BOOL влияет лишь на стиль «от центра / нет», не на величину). +Координаты — локальная СК вида, мм. Размер «свободный» (по `Xc/Yc/Radius`, без `BaseObject`). + +### Угловой + +``` +IAngleDimension ad = symbols.AngleDimensions.Add(DrawingObjectTypeEnum.ksDrADimension); // =10 +ad.Xc = 0; ad.Yc = 0; // вершина угла +ad.X1 = 10; ad.Y1 = 0; // точка на стороне 1 → луч (Xc,Yc)→(X1,Y1) +ad.X2 = 0; ad.Y2 = 10; // точка на стороне 2 → луч (Xc,Yc)→(X2,Y2) +ad.X3 = 5; ad.Y3 = 5; // положение размерной дуги — ЗАДАЁТ ИЗМЕРЯЕМЫЙ СЕКТОР + радиус дуги +ad.DimensionType = ksADMinAngle; // 0=min / 1=max / 2=more180 +((IDimensionText)ad).AutoNominalValue = true; +ad.Update(); // True, ad.Valid == True +double value = ((IDimensionText)ad).NominalValue; // == 90 — ГРАДУСЫ +``` + +**Проверено:** +- вершина(0,0), точки (10,0)&(0,10), `X3/Y3`=(5,5) → **90°** ✓; +- вершина(0,0), точки (10,0)&(10,10), `X3/Y3`=(8,4) → **45°** ✓. +- **`X3/Y3` критичен:** без него тот же угол измерился как **270°** (дополнительный сектор). Точка + положения дуги задаёт, какой из двух секторов меряется; заодно радиус дуги пересчитывается в + `dist(вершина, X3/Y3)` (заданный `Radius` перетирается → его не экспонируем). +- Подход через `Angle1/Angle2` (углы наклона сторон) — **вырожденный** (точки схлопываются к + вершине, `X1≈0.001`), не используем. Геометрию ведут только координаты точек. +- `NominalValue` — в **градусах**. + +`Add(DrawingObjectTypeEnum)` требует код типа: `ksDrADimension = 10` (обычный угловой), +`ksDrABreakDimension = 39` (с обрывом — вне объёма). + +## MCP-инструменты + +| Инструмент | Параметры | Поведение | +|---|---|---| +| `drawing_add_radial_dimension` | `xc,yc` (центр окружности), `radius`, `angle=0` (°), `viewNumber=0` | Радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом `radius` (ЛСК вида, мм). `angle` — направление выноски в ГРАДУСАХ (внутри → радианы). `viewNumber`: 0 = первый/главный. Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида. | +| `drawing_add_angular_dimension` | `xc,yc` (вершина), `x1,y1` (точка на стороне 1), `x2,y2` (точка на стороне 2), `x3,y3` (положение размерной дуги — задаёт сектор), `angleType="min"`, `viewNumber=0` | Угловой размер между лучами (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) (ЛСК вида, мм). `x3,y3` должны лежать в измеряемом секторе. `angleType`: min/max/more. Значение (градусы) измеряется автоматически. Возвращает угол (°) и номер вида. | + +Углы выноски (`angle`) экспонируются в **градусах** (LLM-дружелюбно), внутри `·π/180`. + +## Архитектура + +В существующем `DrawingService` (рядом с `AddDiametralDimensionAsync`); переиспользуем +`RequireSymbols2DContainer(viewNumber)`, `DrawingDimensionResult`, паттерн отката. + +- `DrawingService.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angleDeg, ct)`. +- `DrawingService.AddAngularDimensionAsync(viewNumber, xc, yc, x1, y1, x2, y2, x3, y3, angleType, ct)`. +- Инструменты `drawing_add_radial_dimension`, `drawing_add_angular_dimension` в `DrawingTools`. +- Helpers `GetViewRadialDimensionCountAsync`, `GetViewAngularDimensionCountAsync` (для тестов — + попадание в вид; размеры в `ISymbols2DContainer.RadialDimensions/AngleDimensions.Count`, не в + `IView.ObjectCount`). +- Новый файл `src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs`: `enum AngleDimensionType {Min,Max,More}` + + static `AngleDimensionTypes.Parse(string)` / `ToKompas(...)` (→ `ksADMinAngle/ksADMaxAngle/ksADMoreAngle`), + по образцу `DimensionOrientations`. + +`DrawingDimensionResult` (Value + ViewNumber) переиспользуется: радиальный `Value`=радиус, угловой +`Value`=градусы. + +## Валидация (чистые static, unit-тест) + +- Радиальный: `RequireFiniteCoords(xc, yc, angleDeg)` + `RequirePositiveRadius(radius)` — уже есть. +- Угловой: `RequireFiniteCoords(xc,yc,x1,y1,x2,y2,x3,y3)` (8 координат) + `RequireDistinctPoints` + четырежды: вершина≠(x1,y1), вершина≠(x2,y2), вершина≠(x3,y3) (иначе дуга нулевого радиуса), + (x1,y1)≠(x2,y2) (#1: совпавшие точки сторон → одинаковые лучи). Все уже есть; новых валидаторов не требуется. + +## Реализация + +**`AddRadialDimensionCore`** (зеркало диаметрального, но значение = радиус): валидация → `RequireSymbols2DContainer` +→ `RadialDimensions.Add()` (null-check) → `Xc/Yc/Radius`, `Angle=DimensionAngles.ToRadians(angleDeg)` (#6), `DimensionType=true` +(стиль R от центра, явно), `AutoNominalValue=true` → `Update()` (FALSE → откат `Delete`+ошибка) → +`Valid` (false → откат) → `value = NominalValue` (радиус); `value<=0 || !IsFinite` → откат+ошибка → +`DrawingDimensionResult{Value, ViewNumber}`. + +**`AddAngularDimensionCore`**: валидация → `RequireSymbols2DContainer` → `AngleDimensions.Add(ksDrADimension)` +(null-check) → `Xc/Yc/X1/Y1/X2/Y2/X3/Y3`, `DimensionType=AngleDimensionTypes.ToKompas(angleType)`, +`AutoNominalValue=true` → `Update()` (FALSE → откат) → `Valid` (false → откат) → `value=NominalValue` +(°); `value<=0 || !IsFinite` → откат+ошибка с подсказкой (вырожденный угол: стороны коллинеарны или +`x3,y3` вне сектора) → `DrawingDimensionResult{Value, ViewNumber}`. Угол в (0,360); защиту по верхней +границе не форсируем (КОМПАС нормализует, `more` может дать >180). + +RCW точечно не освобождаем (консистентно с линейным/диаметральным; долг v2-2). + +## Тестирование + +### Unit (`DrawingDimensionTypeTests` — новый, либо в `DrawingValidationTests`) +- `AngleDimensionTypes.Parse`: `min/max/more` (+ рус. синонимы) → enum; неизвестное → `ArgumentException`. +- `AngleDimensionTypes.ToKompas`: маппинг трёх значений → `ksADMinAngle/ksADMaxAngle/ksADMoreAngle`. +- (`ToRadians`, `RequirePositiveRadius`, координаты — уже покрыты.) + +### Integration (`DrawingRadialTests`, `DrawingAngularTests`; наследуют `IntegrationTestBase`) +Радиальный: +1. **Значение = радиус**: `radius=12 → Value ≈ 12` (промотированный спайк). +2. **Попадание в вид**: `RadialDimensions.Count` целевого вида +1 (адресация по `viewNumber`). +3. **Радиус ≤ 0** → `ArgumentOutOfRangeException` (до `Add`). +4. **Нет видов** → понятная ошибка. + +Угловой: +5. **Измеряет 90°**: вершина(0,0), (10,0)&(0,10), x3/y3=(5,5) → `Value ≈ 90`. +6. **Измеряет 45°**: вершина(0,0), (10,0)&(10,10), x3/y3=(8,4) → `Value ≈ 45`. +7. **Попадание в вид**: `AngleDimensions.Count` целевого вида +1. +8. **Выбор угла типом** (#2): та же геометрия 90°, но `angleType=More` → `Value ≈ 270` + (документированное поведение: какой из двух углов измерять, выбирает `angleType`, не `x3,y3`). +9. **Вырожденный угол до Add** (вершина совпадает с точкой стороны) → `ArgumentException`. +10. **Откат после Add** (#3): коллинеарные сонаправленные лучи (0,0),(10,0),(20,0) → угол ≈0 → + `InvalidOperationException`; `AngleDimensions.Count` целевого вида НЕ изменился (откат `Delete`). +11. **Несуществующий `viewNumber>0`** (#4) → понятная ошибка (ветка `FindView`). +12. **Нет видов** → понятная ошибка. + +(Радиальный — аналогично добавлен тест на несуществующий `viewNumber>0`, #4.) + +## Дальнейшее (вне спека) +- Ассоциативная привязка размеров к геометрии вида (`BaseObject`/`BaseObject1/2`); рамка/формат листа; + текстовые обозначения (шероховатость `Roughs`, допуски формы `Tolerances`, выноски `Leaders`, + тех. требования); радиальный с изломом (`BreakRadialDimensions`), угловой с обрывом (`ksDrABreakDimension`). diff --git a/src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs b/src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs new file mode 100644 index 0000000..fe86d67 --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs @@ -0,0 +1,42 @@ +using Kompas6Constants; + +namespace Kompas.Mcp.Core.Drawings; + +/// Тип углового размера — какой из двух секторов измерять +/// (обёртка над ). +public enum AngleDimensionType +{ + /// Минимальный (острый) угол. + Min, + /// Максимальный (тупой) угол. + Max, + /// Угол более 180° (рефлексный). + More, +} + +/// Маппинг типа углового размера ↔ перечисление КОМПАС и разбор строки. +public static class AngleDimensionTypes +{ + public static ksAngleDimTypeEnum ToKompas(AngleDimensionType type) => type switch + { + AngleDimensionType.Min => ksAngleDimTypeEnum.ksADMinAngle, + AngleDimensionType.Max => ksAngleDimTypeEnum.ksADMaxAngle, + AngleDimensionType.More => ksAngleDimTypeEnum.ksADMoreAngle, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Неизвестный тип углового размера."), + }; + + /// Разобрать строку типа углового размера (англ./рус.) в . + public static AngleDimensionType Parse(string value) + { + ArgumentNullException.ThrowIfNull(value); + return value.Trim().ToLowerInvariant() switch + { + "min" or "minimal" or "минимальный" or "острый" => AngleDimensionType.Min, + "max" or "maximal" or "максимальный" or "тупой" => AngleDimensionType.Max, + "more" or "more180" or "больше180" or "рефлексный" => AngleDimensionType.More, + _ => throw new ArgumentException( + $"Неизвестный тип углового размера '{value}'. Допустимо: min, max, more.", + nameof(value)), + }; + } +} diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs b/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs index 3ccc082..b8f40d4 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs @@ -3,7 +3,7 @@ namespace Kompas.Mcp.Core.Drawings; /// Результат простановки размера на чертеже. public sealed record DrawingDimensionResult { - /// Измеренное значение размера (мм). + /// Измеренное значение размера: линейный/диаметральный/радиальный — мм, угловой — градусы. public required double Value { get; init; } /// Номер вида, на котором поставлен размер. diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs index 8cb8b6c..37a2afb 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -184,6 +184,109 @@ public sealed class DrawingService public Task GetViewDiametralDimensionCountAsync(int viewNumber, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.DiametralDimensions?.Count ?? 0, ct); + /// + /// Поставить радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом + /// (ЛОКАЛЬНАЯ СК вида, мм) на виде. — направление выноски (градусы). + /// Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида. + /// + 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); + + private DrawingDimensionResult AddRadialDimensionCore(int viewNumber, double xc, double yc, + double radius, double angleDeg) + { + DrawingValidation.RequireFiniteCoords(xc, yc); + DrawingValidation.RequirePositiveRadius(radius); + var angleRad = DimensionAngles.ToRadians(angleDeg); + + var (view, symbols) = RequireSymbols2DContainer(viewNumber); + var dim = symbols.RadialDimensions?.Add() + ?? throw new InvalidOperationException("RadialDimensions.Add вернул null."); + try + { + dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; dim.Angle = angleRad; + dim.DimensionType = true; // стиль R «от центра» (на величину не влияет — спайк) + ((IDimensionText)dim).AutoNominalValue = true; + + if (!dim.Update()) + throw new InvalidOperationException("IRadialDimension.Update вернул FALSE — размер не построен."); + if (!dim.Valid) + throw new InvalidOperationException("Радиальный размер построен невалидным."); + + var value = ((IDimensionText)dim).NominalValue; // = радиус (не диаметр — спайк) + if (value <= 0 || !double.IsFinite(value)) // инвариант радиуса: строго положительный + throw new InvalidOperationException($"Радиальный размер измерил некорректное значение ({value})."); + return new DrawingDimensionResult { Value = value, ViewNumber = view.Number }; + } + catch + { + try { dim.Delete(); } catch { /* лучшее усилие */ } + throw; + } + } + + /// Число радиальных размеров на виде (для проверки попадания в вид — они в + /// ISymbols2DContainer, а не в IView.ObjectCount). + public Task GetViewRadialDimensionCountAsync(int viewNumber, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.RadialDimensions?.Count ?? 0, ct); + + /// + /// Поставить угловой размер между лучами (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) (ЛОКАЛЬНАЯ СК вида, мм) + /// на виде. (x3,y3) — положение размерной дуги (задаёт её радиус = расстояние от вершины). + /// выбирает, какой из двух углов измерять (min/max/more) — например + /// 90° или 270° для перпендикуляра. Значение (градусы) измеряется автоматически. Возвращает угол (°) и номер вида. + /// + public Task AddAngularDimensionAsync(int viewNumber, double xc, double yc, + double x1, double y1, double x2, double y2, double x3, double y3, + AngleDimensionType angleType, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + AddAngularDimensionCore(viewNumber, xc, yc, x1, y1, x2, y2, x3, y3, angleType), ct); + + private DrawingDimensionResult AddAngularDimensionCore(int viewNumber, double xc, double yc, + double x1, double y1, double x2, double y2, double x3, double y3, AngleDimensionType angleType) + { + DrawingValidation.RequireFiniteCoords(xc, yc, x1, y1, x2, y2, x3, y3); + DrawingValidation.RequireDistinctPoints(xc, yc, x1, y1); // вершина ≠ точка стороны 1 + DrawingValidation.RequireDistinctPoints(xc, yc, x2, y2); // вершина ≠ точка стороны 2 + DrawingValidation.RequireDistinctPoints(xc, yc, x3, y3); // вершина ≠ положение дуги (R дуги > 0) + DrawingValidation.RequireDistinctPoints(x1, y1, x2, y2); // стороны не совпадают (одинаковые лучи) + + var (view, symbols) = RequireSymbols2DContainer(viewNumber); + var dim = symbols.AngleDimensions?.Add(DrawingObjectTypeEnum.ksDrADimension) + ?? throw new InvalidOperationException("AngleDimensions.Add вернул null."); + try + { + dim.Xc = xc; dim.Yc = yc; + dim.X1 = x1; dim.Y1 = y1; dim.X2 = x2; dim.Y2 = y2; dim.X3 = x3; dim.Y3 = y3; + dim.DimensionType = AngleDimensionTypes.ToKompas(angleType); + ((IDimensionText)dim).AutoNominalValue = true; + + if (!dim.Update()) + throw new InvalidOperationException("IAngleDimension.Update вернул FALSE — размер не построен."); + if (!dim.Valid) + throw new InvalidOperationException("Угловой размер построен невалидным."); + + var value = ((IDimensionText)dim).NominalValue; // градусы + // Нулевой/некорректный угол — стороны коллинеарны (сонаправлены) или (x3,y3) вне сектора. + if (value <= 0 || !double.IsFinite(value)) + throw new InvalidOperationException( + $"Угловой размер измерил некорректное значение ({value}°). Стороны коллинеарны или " + + "положение дуги (x3,y3) задаёт вырожденный сектор."); + return new DrawingDimensionResult { Value = value, ViewNumber = view.Number }; + } + catch + { + try { dim.Delete(); } catch { /* лучшее усилие */ } + throw; + } + } + + /// Число угловых размеров на виде (для проверки попадания в вид — они в + /// ISymbols2DContainer, а не в IView.ObjectCount). + public Task GetViewAngularDimensionCountAsync(int viewNumber, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.AngleDimensions?.Count ?? 0, ct); + /// Вид + его 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 93cbec5..def3692 100644 --- a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs +++ b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs @@ -83,4 +83,48 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing) var r = await drawing.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angle); return $"Диаметральный размер поставлен на виде {r.ViewNumber}, Ø{r.Value:0.###} мм."; } + + [McpServerTool(Name = "drawing_add_radial_dimension")] + [Description("Поставить радиальный размер (R) окружности/дуги на виде активного чертежа. Окружность " + + "задаётся центром (xc,yc) и радиусом radius в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты " + + "модели на плоскости вида) — задайте параметры реальной окружности вида. angle — направление выноски " + + "в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). " + + "Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида.")] + public async Task AddRadialDimension( + [Description("X центра окружности (СК вида, мм)")] double xc, + [Description("Y центра окружности")] double yc, + [Description("Радиус окружности, мм")] double radius, + [Description("Направление выноски, градусы")] double angle = 0, + [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0) + { + await session.ConnectAsync(); + var r = await drawing.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angle); + return $"Радиальный размер поставлен на виде {r.ViewNumber}, R{r.Value:0.###} мм."; + } + + [McpServerTool(Name = "drawing_add_angular_dimension")] + [Description("Поставить угловой размер между двумя сторонами на виде активного чертежа. Угол задаётся " + + "лучами из вершины (xc,yc): сторона 1 проходит через точку (x1,y1), сторона 2 — через (x2,y2) " + + "(всё в ЛОКАЛЬНОЙ СК вида, мм). Точка (x3,y3) — положение размерной дуги (задаёт её радиус = " + + "расстояние от вершины); поместите её в стороне измеряемого угла. КАКОЙ из двух углов измерять " + + "(например 90° или 270° для перпендикуляра) — выбирает angleType: min (меньший/острый) | max " + + "(больший/тупой) | more (рефлексный, >180°). viewNumber — номер вида (0 = первый/главный). " + + "Значение (градусы) измеряется автоматически. Возвращает угол (°) и номер вида.")] + public async Task AddAngularDimension( + [Description("X вершины угла (СК вида, мм)")] double xc, + [Description("Y вершины угла")] double yc, + [Description("X точки на стороне 1")] double x1, + [Description("Y точки на стороне 1")] double y1, + [Description("X точки на стороне 2")] double x2, + [Description("Y точки на стороне 2")] double y2, + [Description("X положения размерной дуги (внутри измеряемого сектора)")] double x3, + [Description("Y положения размерной дуги")] double y3, + [Description("Тип угла: min | max | more")] string angleType = "min", + [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0) + { + await session.ConnectAsync(); + var t = AngleDimensionTypes.Parse(angleType); + var r = await drawing.AddAngularDimensionAsync(viewNumber, xc, yc, x1, y1, x2, y2, x3, y3, t); + return $"Угловой размер поставлен на виде {r.ViewNumber}, {r.Value:0.###}°."; + } } diff --git a/tests/Kompas.Mcp.Tests/AngleDimensionTypesTests.cs b/tests/Kompas.Mcp.Tests/AngleDimensionTypesTests.cs new file mode 100644 index 0000000..b315145 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/AngleDimensionTypesTests.cs @@ -0,0 +1,30 @@ +using Kompas.Mcp.Core.Drawings; + +namespace Kompas.Mcp.Tests; + +[Trait("Category", "Unit")] +public sealed class AngleDimensionTypesTests +{ + [Theory] + [InlineData("min", AngleDimensionType.Min)] + [InlineData("max", AngleDimensionType.Max)] + [InlineData("more", AngleDimensionType.More)] + [InlineData(" MIN ", AngleDimensionType.Min)] + [InlineData("минимальный", AngleDimensionType.Min)] + [InlineData("максимальный", AngleDimensionType.Max)] + [InlineData("больше180", AngleDimensionType.More)] + public void Parse_maps_known(string value, AngleDimensionType expected) + => Assert.Equal(expected, AngleDimensionTypes.Parse(value)); + + [Fact] + public void Parse_throws_on_unknown() + => Assert.Throws(() => AngleDimensionTypes.Parse("acute")); + + [Fact] + public void Parse_throws_on_null() + => Assert.Throws(() => AngleDimensionTypes.Parse(null!)); + + // ToKompas (→ ksAngleDimTypeEnum) намеренно НЕ покрыт unit-тестом: тестовый проект не ссылается + // на Kompas6Constants (граница проекта, как у DimensionOrientations.ToKompas). Маппинг тривиален + // и проверяется интеграцией (min → острый угол 90°, выбор сектора). +} diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs new file mode 100644 index 0000000..e582821 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs @@ -0,0 +1,178 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; +using Kompas.Mcp.Core.Modeling; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: угловой размер на виде (API7 ISymbols2DContainer.AngleDimensions). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class DrawingAngularTests : IntegrationTestBase +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly DrawingService _drawing; + + public DrawingAngularTests(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 BuildAndSaveBoxAsync() + { + var path = TestPaths.NewFile(".m3d"); + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + var s = await _modeler.OpenSketchAsync(BasePlane.XOY); + await _modeler.AddRectangleAsync(s, 0, 0, 40, 30); + 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; + } + + private async Task NewDrawingWithViewsAsync() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + return partFile; + } + + [Fact] + public async Task Angular_measures_right_angle() + { + await NewDrawingWithViewsAsync(); + try + { + // Вершина (0,0); сторона1 → (10,0); сторона2 → (0,10); дуга в секторе (5,5) → 90°. + var r = await _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 10, y1: 0, x2: 0, y2: 10, x3: 5, y3: 5, + angleType: AngleDimensionType.Min); + Assert.InRange(r.Value, 89.5, 90.5); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Angular_measures_45_degrees() + { + await NewDrawingWithViewsAsync(); + try + { + // Вершина (0,0); сторона1 → (10,0); сторона2 → (10,10) (45°); дуга (8,4). + var r = await _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 10, y1: 0, x2: 10, y2: 10, x3: 8, y3: 4, + angleType: AngleDimensionType.Min); + Assert.InRange(r.Value, 44.5, 45.5); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Targets_specific_view_and_increments_angular_count() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var targetNumber = views.ViewNumbers[1]; + + var before = await _drawing.GetViewAngularDimensionCountAsync(targetNumber); + var r = await _drawing.AddAngularDimensionAsync( + targetNumber, xc: 0, yc: 0, x1: 10, y1: 0, x2: 0, y2: 10, x3: 5, y3: 5, + angleType: AngleDimensionType.Min); + var after = await _drawing.GetViewAngularDimensionCountAsync(targetNumber); + + Assert.Equal(targetNumber, r.ViewNumber); + Assert.Equal(before + 1, after); + Assert.InRange(r.Value, 89.5, 90.5); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Angle_type_more_measures_reflex_angle() + { + await NewDrawingWithViewsAsync(); + try + { + // Та же геометрия 90°, но angleType=more → измеряется рефлексный угол ~270°. + // Тип угла (min/max/more) выбирает, какой из секторов измерять; (x3,y3) лишь + // позиционирует размерную дугу (её ставим в рефлексный сектор для согласованности). + var r = await _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 10, y1: 0, x2: 0, y2: 10, x3: -5, y3: -5, + angleType: AngleDimensionType.More); + Assert.InRange(r.Value, 269.0, 271.0); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Coincident_vertex_and_side_throws() + { + await NewDrawingWithViewsAsync(); + try + { + // Вершина совпадает с точкой стороны1 → вырождение до Add. + await Assert.ThrowsAsync(() => + _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 0, y1: 0, x2: 0, y2: 10, x3: 5, y3: 5, + angleType: AngleDimensionType.Min)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Collinear_rays_rollback_leaves_count_unchanged() + { + await NewDrawingWithViewsAsync(); + try + { + var before = await _drawing.GetViewAngularDimensionCountAsync(0); + // Сонаправленные лучи (0,0)→(10,0) и (0,0)→(20,0): угол ≈0 → пост-проверка → откат. + await Assert.ThrowsAsync(() => + _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 10, y1: 0, x2: 20, y2: 0, x3: 15, y3: 5, + angleType: AngleDimensionType.Min)); + var after = await _drawing.GetViewAngularDimensionCountAsync(0); + Assert.Equal(before, after); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task No_views_throws() + { + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await Assert.ThrowsAsync(() => + _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 10, y1: 0, x2: 0, y2: 10, x3: 5, y3: 5, + angleType: AngleDimensionType.Min)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Unknown_view_number_throws() + { + await NewDrawingWithViewsAsync(); + try + { + await Assert.ThrowsAsync(() => + _drawing.AddAngularDimensionAsync( + viewNumber: 999, xc: 0, yc: 0, x1: 10, y1: 0, x2: 0, y2: 10, x3: 5, y3: 5, + angleType: AngleDimensionType.Min)); + } + finally { await _docs.CloseAsync(save: false); } + } +} diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingRadialTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingRadialTests.cs new file mode 100644 index 0000000..f5d0ae6 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingRadialTests.cs @@ -0,0 +1,115 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; +using Kompas.Mcp.Core.Modeling; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: радиальный размер (R) на виде (API7 ISymbols2DContainer.RadialDimensions). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class DrawingRadialTests : IntegrationTestBase +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly DrawingService _drawing; + + public DrawingRadialTests(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 BuildAndSaveBoxAsync() + { + var path = TestPaths.NewFile(".m3d"); + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + var s = await _modeler.OpenSketchAsync(BasePlane.XOY); + await _modeler.AddRectangleAsync(s, 0, 0, 40, 30); + 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 Radial_dimension_value_is_radius() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + // Радиус 12 → значение радиального = радиус (НЕ диаметр), подтверждено спайком. + var r = await _drawing.AddRadialDimensionAsync(viewNumber: 0, xc: 10, yc: 10, radius: 12, angleDeg: 45); + Assert.InRange(r.Value, 11.9, 12.1); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Targets_specific_view_and_increments_radial_count() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var targetNumber = views.ViewNumbers[1]; + + var before = await _drawing.GetViewRadialDimensionCountAsync(targetNumber); + var r = await _drawing.AddRadialDimensionAsync(targetNumber, xc: 5, yc: 5, radius: 8, angleDeg: 30); + var after = await _drawing.GetViewRadialDimensionCountAsync(targetNumber); + + Assert.Equal(targetNumber, r.ViewNumber); + Assert.Equal(before + 1, after); + Assert.InRange(r.Value, 7.9, 8.1); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Non_positive_radius_throws() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + await Assert.ThrowsAsync(() => + _drawing.AddRadialDimensionAsync(viewNumber: 0, xc: 0, yc: 0, radius: 0, angleDeg: 0)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task No_views_throws() + { + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await Assert.ThrowsAsync(() => + _drawing.AddRadialDimensionAsync(viewNumber: 0, xc: 0, yc: 0, radius: 10, angleDeg: 0)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Unknown_view_number_throws() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + await Assert.ThrowsAsync(() => + _drawing.AddRadialDimensionAsync(viewNumber: 999, xc: 0, yc: 0, radius: 10, angleDeg: 0)); + } + finally { await _docs.CloseAsync(save: false); } + } +} From 86a5a523476490ba96477e819196a0c62c479249 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 19:58:15 +0300 Subject: [PATCH 2/3] =?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(=D1=83=D0=B3=D0=BB=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=BC=D0=B5=D1=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - описание (x3,y3) и сообщение об ошибке: угол выбирает angleType, не точка дуги - тест angleType=Max (лучи 0°/45° → 135° тупой супплемент): min=45/max=135/more=315 - спек дополнен находкой о трёх типах угла 241 тест зелёный, сборка Release чистая. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...drawing-radial-angular-dimension-design.md | 27 +++++++++++++++---- .../Drawings/DrawingService.cs | 6 ++--- src/Kompas.Mcp.Host/Tools/DrawingTools.cs | 2 +- .../Integration/DrawingAngularTests.cs | 16 +++++++++++ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md b/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md index 35a1331..95af0ad 100644 --- a/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md +++ b/docs/superpowers/specs/2026-05-27-drawing-radial-angular-dimension-design.md @@ -6,10 +6,27 @@ ## Уточнение по реализации (важно) Спайк изначально решил, что измеряемый сектор выбирает положение дуги `(x3,y3)`. **При реализации -выяснилось иначе:** сектор (90° vs 270°) выбирает **`angleType`** (`ksADMinAngle`/`MaxAngle`/`MoreAngle`), +выяснилось иначе:** какой угол измерять, выбирает **`angleType`** (`ksADMinAngle`/`MaxAngle`/`MoreAngle`), а `(x3,y3)` лишь позиционирует размерную дугу и задаёт её радиус. (270° в спайке возникал потому, что -`X3/Y3` не были заданы = совпадали с вершиной → вырождение.) Проверено: `angleType=Min`→90°, -`angleType=More`→270° на одной геометрии. Тест #8 и описания инструмента/сервиса исправлены под это. +`X3/Y3` не были заданы = совпадали с вершиной → вырождение.) + +Три типа дают **три разных угла** (проверено на лучах 0° и 45°): +- `Min` (`ksADMinAngle`) → **45°** (острый); +- `Max` (`ksADMaxAngle`) → **135°** (тупой супплемент, 180−45); +- `More` (`ksADMoreAngle`) → **315°** (рефлексный, 360−45). + +Тесты (#8 More→270 на 90°, Max→135 на 45°) и описания инструмента/сервиса исправлены под это. + +## Правки по ревью Codex (реализация) + +- **#1** описание параметра `x3,y3` в инструменте переформулировано (точка позиционирует дугу; угол + выбирает `angleType`, не она). +- **#2** сообщение пост-проверки углового больше не винит `(x3,y3)` — указывает на коллинеарность сторон. +- **#3** добавлен интеграционный тест `angleType=Max` (лучи 0°/45° → 135°) — покрыл маппинг `ksADMaxAngle`, + отличный от `Min`/`More`. +- **#4** (откат радиального не тестируется) — **отклонено с обоснованием:** как у диаметрального + (`radius>0` → всегда `Valid`, `value=radius>0`; детерминированного пост-Add сбоя нет), код отката + структурно идентичен протестированному угловому (коллинеарный путь, счётчик не меняется). ## Правки по ревью Codex (спек) @@ -149,8 +166,8 @@ RCW точечно не освобождаем (консистентно с ли 5. **Измеряет 90°**: вершина(0,0), (10,0)&(0,10), x3/y3=(5,5) → `Value ≈ 90`. 6. **Измеряет 45°**: вершина(0,0), (10,0)&(10,10), x3/y3=(8,4) → `Value ≈ 45`. 7. **Попадание в вид**: `AngleDimensions.Count` целевого вида +1. -8. **Выбор угла типом** (#2): та же геометрия 90°, но `angleType=More` → `Value ≈ 270` - (документированное поведение: какой из двух углов измерять, выбирает `angleType`, не `x3,y3`). +8. **Выбор угла типом** (#2): `angleType=More` на геометрии 90° → `Value ≈ 270`; `angleType=Max` + на лучах 0°/45° → `Value ≈ 135` (тупой супплемент). Какой угол измерять, выбирает `angleType`, не `x3,y3`. 9. **Вырожденный угол до Add** (вершина совпадает с точкой стороны) → `ArgumentException`. 10. **Откат после Add** (#3): коллинеарные сонаправленные лучи (0,0),(10,0),(20,0) → угол ≈0 → `InvalidOperationException`; `AngleDimensions.Count` целевого вида НЕ изменился (откат `Delete`). diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs index 37a2afb..ba87a2f 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -268,11 +268,11 @@ public sealed class DrawingService throw new InvalidOperationException("Угловой размер построен невалидным."); var value = ((IDimensionText)dim).NominalValue; // градусы - // Нулевой/некорректный угол — стороны коллинеарны (сонаправлены) или (x3,y3) вне сектора. + // Нулевой/некорректный угол — стороны коллинеарны (лучи сонаправлены → угол вырожден). if (value <= 0 || !double.IsFinite(value)) throw new InvalidOperationException( - $"Угловой размер измерил некорректное значение ({value}°). Стороны коллинеарны или " + - "положение дуги (x3,y3) задаёт вырожденный сектор."); + $"Угловой размер измерил некорректное значение ({value}°) — стороны коллинеарны " + + "(лучи (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) сонаправлены)."); return new DrawingDimensionResult { Value = value, ViewNumber = view.Number }; } catch diff --git a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs index def3692..b74aba3 100644 --- a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs +++ b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs @@ -117,7 +117,7 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing) [Description("Y точки на стороне 1")] double y1, [Description("X точки на стороне 2")] double x2, [Description("Y точки на стороне 2")] double y2, - [Description("X положения размерной дуги (внутри измеряемого сектора)")] double x3, + [Description("X положения размерной дуги (отстоит от вершины; угол выбирает angleType, не эта точка)")] double x3, [Description("Y положения размерной дуги")] double y3, [Description("Тип угла: min | max | more")] string angleType = "min", [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0) diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs index e582821..31ae332 100644 --- a/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingAngularTests.cs @@ -75,6 +75,22 @@ public sealed class DrawingAngularTests : IntegrationTestBase finally { await _docs.CloseAsync(save: false); } } + [Fact] + public async Task Angle_type_max_measures_larger_angle() + { + await NewDrawingWithViewsAsync(); + try + { + // Лучи 0° и 45°: три типа дают разные углы — min=45 (острый), max=135 (тупой + // супплемент 180−45), more=315 (рефлексный). Покрывает маппинг ksADMaxAngle (≠ Min/More). + var r = await _drawing.AddAngularDimensionAsync( + viewNumber: 0, xc: 0, yc: 0, x1: 10, y1: 0, x2: 10, y2: 10, x3: 4, y3: -8, + angleType: AngleDimensionType.Max); + Assert.InRange(r.Value, 134.0, 136.0); + } + finally { await _docs.CloseAsync(save: false); } + } + [Fact] public async Task Targets_specific_view_and_increments_angular_count() { From db9d76a7bd31cdd388612462d185bb28dd6748e9 Mon Sep 17 00:00:00 2001 From: Shahovalov MIkhail Date: Wed, 27 May 2026 20:03:35 +0300 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20=D1=80=D0=B0=D0=B4=D0=B8=D0=B0?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B8=20=D1=83=D0=B3=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D1=8B=20=E2=80=94=20README,=20ARCHITECTURE,=20OPEN=5FQUESTIONS?= =?UTF-8?q?,=20presentation,=20CLAUDE.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 78 инструментов, 241 тест. Базовое семейство размеров 2D-чертежа завершено (линейный, диаметральный, радиальный, угловой). Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 4 ++-- README.md | 6 +++--- docs/ARCHITECTURE.md | 12 +++++++----- docs/OPEN_QUESTIONS.md | 11 ++++++++++- docs/presentation.html | 17 +++++++++-------- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2c48391..758eccf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,7 @@ The base is committed to the repo (canonical) — no regeneration step. It cover **v1 + v2 + STEP/assembly + direct B-rep edit + structural model inspection implemented and working** (full sketch→feature→inspect→STEP round-trip + move_face + describe_model validated end-to-end). Stack: **.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**. -**76 MCP tools, 218 tests green (141 unit + 77 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, измеряется автоматически; возвращает диаметр и номер вида. +**78 MCP tools, 241 tests green (150 unit + 91 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`; значение (градусы) измеряется автоматически; возвращает угол (°) и номер вида. See [`README.md`](README.md) and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); design decisions/caveats in [`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). @@ -57,7 +57,7 @@ Key implementation facts (don't relearn): - **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) are done.** Not yet done: parametric sketches — **investigated, unavailable via COM API** (`ksCDimWithVariable` cannot be constructed from external automation; see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`); 2D-чертёж: радиальные/угловые размеры, рамка/формат — далее (линейные и диаметральный размеры реализованы); 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/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)` лишь позиционирует дугу, угол НЕ выбирает) are done.** Базовое семейство размеров 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`), рамка/формат листа, текстовые обозначения (шероховатость `Roughs`, допуски формы `Tolerances`, выноски `Leaders`, тех. требования) — далее; 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`. diff --git a/README.md b/README.md index 7e2e082..70f7486 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe } ``` -## Инструменты (76 инструментов) +## Инструменты (78 инструментов) | Группа | Инструменты | |---|---| @@ -54,7 +54,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe | Variables | `create_variable`, `set_variable`, `delete_variable` | | Conversion | `import_step`, `export_step` | | Assembly | `assembly_add_component`, `assembly_add_mate` | -| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension`, `drawing_add_diametral_dimension` | +| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension`, `drawing_add_diametral_dimension`, `drawing_add_radial_dimension`, `drawing_add_angular_dimension` | | Validation | `validate_part` | ### Типовой сценарий @@ -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/ 218 тестов: unit + integration (integration требуют КОМПАС) +tests/Kompas.Mcp.Tests/ 241 тест: 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 41b9381..6fb576e 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` (веха «2D-чертёж», инкремент 4)). Актуализировано по коду. +> Статус: **реализовано и работает** (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` (веха «2D-чертёж», инкремент 5)). Актуализировано по коду. > Сопутствующий контекст по 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) + интеграционные (требуют КОМПАС). 218 тестов (141 unit + 77 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | +| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 241 тест (150 unit + 91 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики. @@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async) --- -## 5. Карта инструментов (76 инструментов) +## 5. Карта инструментов (78 инструментов) Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`. @@ -213,6 +213,8 @@ MCP client ──stdio──> Host (поток-пул, async) - `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_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`. **Vision — визуальная обратная связь (fallback)** - `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**, @@ -347,14 +349,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):** документы, эскизы (полный набор 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`). +**Реализовано (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):** документы, эскизы (полный набор 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`). Класс «2D-чертёж» завершён: стандартные виды, основная надпись, размеры — линейный, диаметральный, радиальный, угловой. **Следующие приоритеты:** 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-чертёж (продолжение):** радиальные/угловые размеры, рамка/формат, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`); основная надпись/штамп реализована (`drawing_fill_title_block`); линейные размеры реализованы (`drawing_add_linear_dimension`); диаметральный размер реализован (`drawing_add_diametral_dimension`). +5. **2D-чертёж (продолжение):** базовое семейство размеров реализовано — линейный, диаметральный, радиальный, угловой (`drawing_add_linear_dimension`, `drawing_add_diametral_dimension`, `drawing_add_radial_dimension`, `drawing_add_angular_dimension`). Нереализовано: ассоциативная привязка размеров к геометрии (`BaseObject`), рамка/формат листа, текстовые обозначения (шероховатость, допуски формы, выноски, технические требования). 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 b9778a2..84d2399 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -199,4 +199,13 @@ stateful-сессии? Пока полагаемся на последовате **Решено.** Новый инструмент `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — ставит линейный размер на виде активного чертежа. Паттерн API7: получить вид по `viewNumber` → COM-QI `(ISymbols2DContainer)view` (размер адресуется конкретному виду, а не листу) → `LineDimensions.Add()` → `ILineDimension` → `X1,Y1,X2,Y2` (выносные точки в **локальной СК вида**, мм), `X3,Y3` (положение размерной линии), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true` → `Update()` → `Valid` → значение `((IDimensionText)dim).NominalValue`. Откат `dim.Delete()` при ошибке или нулевом значении. **Ключевая находка: размеры НЕ входят в `IView.ObjectCount`** (тот считает геометрию `IDrawingContainer`); размеры живут в `ISymbols2DContainer` отдельно — попадание размера в вид проверяется через `ISymbols2DContainer.LineDimensions.Count`. `DrawingViewsResult` расширен полем `ViewNumbers` (номера созданных видов — для адресации при простановке размеров). Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. Итог: **75 инструментов, 203 теста (131 unit + 72 integration).** Следующий шаг: диаметральные/радиальные/угловые размеры, рамка/формат. ### ✅ Диаметральный размер на виде чертежа — реализован (инкремент 4 вехи «2D-чертёж») -**Решено.** Новый инструмент `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber)` — ставит диаметральный размер (Ø) на виде активного чертежа. Паттерн API7: общий хелпер `RequireSymbols2DContainer(viewNumber)` (переиспользуется с линейным путём) → `DiametralDimensions.Add()` → `IDiametralDimension` (`Xc`, `Yc`, `Radius` в локальной СК вида, `Angle` в **радианах** через `DimensionAngles.ToRadians`, `AutoNominalValue=true`) → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue` = диаметр (2·Radius). Размер «свободный» (без `BaseObject` — ассоциативная привязка отложена). Валидатор `DrawingValidation.RequirePositiveRadius`. `DimensionAngles.ToRadians` (с проверкой переполнения) добавлен в `DimensionOrientation.cs`; `RequirePositiveRadius` — в `DrawingValidation.cs`; инструмент в `DrawingTools.cs`. Итог: **76 инструментов, 218 тестов (141 unit + 77 integration).** Следующий шаг: радиальные/угловые размеры, рамка/формат. +**Решено.** Новый инструмент `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber)` — ставит диаметральный размер (Ø) на виде активного чертежа. Паттерн API7: общий хелпер `RequireSymbols2DContainer(viewNumber)` (переиспользуется с линейным путём) → `DiametralDimensions.Add()` → `IDiametralDimension` (`Xc`, `Yc`, `Radius` в локальной СК вида, `Angle` в **радианах** через `DimensionAngles.ToRadians`, `AutoNominalValue=true`) → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue` = диаметр (2·Radius). Размер «свободный» (без `BaseObject` — ассоциативная привязка отложена). Валидатор `DrawingValidation.RequirePositiveRadius`. `DimensionAngles.ToRadians` (с проверкой переполнения) добавлен в `DimensionOrientation.cs`; `RequirePositiveRadius` — в `DrawingValidation.cs`; инструмент в `DrawingTools.cs`. Итог: **76 инструментов, 218 тестов (141 unit + 77 integration).** + +### ✅ Радиальный и угловой размеры на виде чертежа — реализованы (инкремент 5 вехи «2D-чертёж») +**Решено.** Два новых инструмента завершают базовое семейство размеров: + +`drawing_add_radial_dimension(xc,yc,radius, angle=0, viewNumber)` — радиальный размер (R). Паттерн API7: `RequireSymbols2DContainer(viewNumber)` → `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="min", viewNumber)` — угловой размер. `angleType` = `"min"` (острый) | `"max"` (тупой) | `"more"` (рефлексный >180°). Паттерн API7: `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-тестов для маппинга). + +Итог: **78 инструментов, 241 тест (150 unit + 91 integration).** Класс «2D-чертёж» — виды, штамп, размеры (линейный, диаметральный, радиальный, угловой) — завершён. Нереализовано: ассоциативная привязка размеров к геометрии (`BaseObject`), рамка/формат листа, текстовые обозначения (шероховатость, допуски формы, выноски, тех. требования). diff --git a/docs/presentation.html b/docs/presentation.html index 25191bd..4bd8e92 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 (76 инструментов) + Статус: 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 (78 инструментов)

КОМПАС-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 · навык kompas-3d · 76 инструментов · 218 тестов
+
Общий прогресс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 · навык kompas-3d · 78 инструментов · 241 тест
@@ -297,7 +297,7 @@

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

-

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

+

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

  • System: connect · status · set_visible
  • Documents: create · open · save · save_as · close · active
  • @@ -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)
  • -
  • 2D-чертёж: радиальные/угловые размеры, рамка/формат · пакет 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, API7) · drawing_add_radial_dimension (радиальный размер R, IRadialDimension, API7) · drawing_add_angular_dimension (угловой размер, IAngleDimension, angleType=min/max/more, API7)
  • +
  • 2D-чертёж: ассоциативная привязка размеров к геометрии (BaseObject), рамка/формат, текстовые обозначения (шероховатость, допуски, выноски, тех. требования) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
@@ -322,7 +322,7 @@
  • Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText
  • Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция
  • -
  • Итого 218 тестов зелёных (141 unit + 77 integration)
  • +
  • Итого 241 тест зелёный (150 unit + 91 integration)
  • Сборка kompas-mcp.exe, README с конфигом клиента
  • Навык kompas-3d (методика) + полигон usecases/
  • In-process тест MCP-клиента, релизный publish
  • @@ -353,8 +353,9 @@
    сборки инкремент 2 · готово ✓

    Сопряжения

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

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

    Стандартные виды + основная надпись

    drawing_create_standard_views (AddStandartViews, API7) · drawing_fill_title_block (IStamp.Text[id].Str, API7) · 74 инструмента · 188 тестов

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

    Линейные размеры

    drawing_add_linear_dimension (ISymbols2DContainer/LineDimensions.Add/ILineDimension, локальная СК вида, API7) · ViewNumbers в результате видов · 75 инструментов · 203 теста

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

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

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

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

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

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

    +
    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 тест

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

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

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