Merge feat/drawing-radial-angular-dimension: радиальный и угловой размеры (инкремент 5)
Завершено базовое семейство размеров 2D-чертежа. 78 инструментов, 241 тест зелёный.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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** — как опция для удалённых клиентов.
|
||||
|
||||
|
||||
+10
-1
@@ -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`), рамка/формат листа, текстовые обозначения (шероховатость, допуски формы, выноски, тех. требования).
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
<!-- HERO -->
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+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 инструментов)</span>
|
||||
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+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 инструментов)</span>
|
||||
<h1>КОМПАС-3D <span class="g">MCP-сервер</span><br>управление CAD языком LLM</h1>
|
||||
<p class="lead">MCP-сервер, который превращает операции КОМПАС-3D — создание документов,
|
||||
эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом —
|
||||
@@ -261,7 +261,7 @@
|
||||
впереди.</p>
|
||||
|
||||
<div class="prog">
|
||||
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · 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 тестов</span></div>
|
||||
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · 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 тест</span></div>
|
||||
<div class="bar"><i style="width:97%"></i></div>
|
||||
</div>
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
<!-- TOOLS -->
|
||||
<div class="panel">
|
||||
<h3>Инструменты v3+ <span class="tag done">ГОТОВО</span></h3>
|
||||
<p class="meta">76 инструментов, отдаются по MCP-протоколу</p>
|
||||
<p class="meta">78 инструментов, отдаются по MCP-протоколу</p>
|
||||
<ul class="list">
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>System</code>: connect · status · set_visible</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Documents</code>: create · open · save · save_as · close · active</span></li>
|
||||
@@ -310,8 +310,8 @@
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Variables</code>: <b>create_variable</b> · <b>set_variable</b> · <b>delete_variable</b> (пакет D)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Conversion</code>: import_step · export_step</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Assembly</code>: <b>assembly_add_component</b> (вставка компонента .m3d/.a3d в сборку, API7) · <b>assembly_add_mate</b> (сопряжения coincidence/distance, API7)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Drawing</code>: <b>drawing_create_standard_views</b> (стандартные ассоциативные виды чертежа, API7) · <b>drawing_fill_title_block</b> (основная надпись/штамп: обозначение, наименование, материал, API7) · <b>drawing_add_linear_dimension</b> (линейный размер на виде, ISymbols2DContainer, API7) · <b>drawing_add_diametral_dimension</b> (диаметральный размер Ø, IDiametralDimension, API7)</span></li>
|
||||
<li><span class="mark wip">→</span><span>2D-чертёж: радиальные/угловые размеры, рамка/формат · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t"><code>Drawing</code>: <b>drawing_create_standard_views</b> (стандартные ассоциативные виды чертежа, API7) · <b>drawing_fill_title_block</b> (основная надпись/штамп: обозначение, наименование, материал, API7) · <b>drawing_add_linear_dimension</b> (линейный размер на виде, ISymbols2DContainer, API7) · <b>drawing_add_diametral_dimension</b> (диаметральный размер Ø, IDiametralDimension, API7) · <b>drawing_add_radial_dimension</b> (радиальный размер R, IRadialDimension, API7) · <b>drawing_add_angular_dimension</b> (угловой размер, IAngleDimension, angleType=min/max/more, API7)</span></li>
|
||||
<li><span class="mark wip">→</span><span>2D-чертёж: ассоциативная привязка размеров к геометрии (BaseObject), рамка/формат, текстовые обозначения (шероховатость, допуски, выноски, тех. требования) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -322,7 +322,7 @@
|
||||
<ul class="list">
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 218 тестов зелёных (141 unit + 77 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 241 тест зелёный (150 unit + 91 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Сборка <code>kompas-mcp.exe</code>, README с конфигом клиента</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Навык <code>kompas-3d</code> (методика) + полигон <code>usecases/</code></span></li>
|
||||
<li><span class="mark todo"></span><span>In-process тест MCP-клиента, релизный publish</span></li>
|
||||
@@ -353,8 +353,9 @@
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">сборки инкремент 2 · готово ✓</div><h4>Сопряжения</h4><p><b>assembly_add_mate</b> (coincidence/distance, API7 IMateConstraints3D) · IntegrationTestBase · 72 инструмента · 156 тестов</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 2 ✓</div><h4>Стандартные виды + основная надпись</h4><p><b>drawing_create_standard_views</b> (AddStandartViews, API7) · <b>drawing_fill_title_block</b> (IStamp.Text[id].Str, API7) · 74 инструмента · 188 тестов</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 3 ✓</div><h4>Линейные размеры</h4><p><b>drawing_add_linear_dimension</b> (ISymbols2DContainer/LineDimensions.Add/ILineDimension, локальная СК вида, API7) · ViewNumbers в результате видов · 75 инструментов · 203 теста</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 4 ✓</div><h4>Диаметральный размер</h4><p><b>drawing_add_diametral_dimension</b> (ISymbols2DContainer.DiametralDimensions.Add/IDiametralDimension, Xc/Yc/Radius/Angle рад, API7) · RequireSymbols2DContainer общий хелпер · DimensionAngles.ToRadians · 76 инструментов · 218 тестов · далее: радиальные/угловые размеры, рамка/формат</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Радиальные/угловые размеры · рамка/формат · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 4 ✓</div><h4>Диаметральный размер</h4><p><b>drawing_add_diametral_dimension</b> (ISymbols2DContainer.DiametralDimensions.Add/IDiametralDimension, Xc/Yc/Radius/Angle рад, API7) · RequireSymbols2DContainer общий хелпер · DimensionAngles.ToRadians · 76 инструментов · 218 тестов</p></div>
|
||||
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 5 ✓</div><h4>Радиальный и угловой размеры</h4><p><b>drawing_add_radial_dimension</b> (IRadialDimension, DimensionType=true, API7) · <b>drawing_add_angular_dimension</b> (IAngleDimension, angleType=min/max/more, AngleDimensionType enum, API7) · базовое семейство размеров завершено · 78 инструментов · 241 тест</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Ассоциативная привязка размеров (BaseObject) · рамка/формат · текстовые обозначения (шероховатость, допуски, выноски) · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# Дизайн: радиальный (R) и угловой размеры на виде чертежа через API7
|
||||
|
||||
**Дата:** 2026-05-27
|
||||
**Статус:** реализовано и проверено (5 радиальных + 8 угловых интеграционных + 9 unit; всего 240 тестов зелёных); ревью Codex спека учтено
|
||||
|
||||
## Уточнение по реализации (важно)
|
||||
|
||||
Спайк изначально решил, что измеряемый сектор выбирает положение дуги `(x3,y3)`. **При реализации
|
||||
выяснилось иначе:** какой угол измерять, выбирает **`angleType`** (`ksADMinAngle`/`MaxAngle`/`MoreAngle`),
|
||||
а `(x3,y3)` лишь позиционирует размерную дугу и задаёт её радиус. (270° в спайке возникал потому, что
|
||||
`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 (спек)
|
||||
|
||||
- **#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): `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`).
|
||||
11. **Несуществующий `viewNumber>0`** (#4) → понятная ошибка (ветка `FindView`).
|
||||
12. **Нет видов** → понятная ошибка.
|
||||
|
||||
(Радиальный — аналогично добавлен тест на несуществующий `viewNumber>0`, #4.)
|
||||
|
||||
## Дальнейшее (вне спека)
|
||||
- Ассоциативная привязка размеров к геометрии вида (`BaseObject`/`BaseObject1/2`); рамка/формат листа;
|
||||
текстовые обозначения (шероховатость `Roughs`, допуски формы `Tolerances`, выноски `Leaders`,
|
||||
тех. требования); радиальный с изломом (`BreakRadialDimensions`), угловой с обрывом (`ksDrABreakDimension`).
|
||||
@@ -0,0 +1,42 @@
|
||||
using Kompas6Constants;
|
||||
|
||||
namespace Kompas.Mcp.Core.Drawings;
|
||||
|
||||
/// <summary>Тип углового размера — какой из двух секторов измерять
|
||||
/// (обёртка над <see cref="ksAngleDimTypeEnum"/>).</summary>
|
||||
public enum AngleDimensionType
|
||||
{
|
||||
/// <summary>Минимальный (острый) угол.</summary>
|
||||
Min,
|
||||
/// <summary>Максимальный (тупой) угол.</summary>
|
||||
Max,
|
||||
/// <summary>Угол более 180° (рефлексный).</summary>
|
||||
More,
|
||||
}
|
||||
|
||||
/// <summary>Маппинг типа углового размера ↔ перечисление КОМПАС и разбор строки.</summary>
|
||||
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, "Неизвестный тип углового размера."),
|
||||
};
|
||||
|
||||
/// <summary>Разобрать строку типа углового размера (англ./рус.) в <see cref="AngleDimensionType"/>.</summary>
|
||||
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)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ namespace Kompas.Mcp.Core.Drawings;
|
||||
/// <summary>Результат простановки размера на чертеже.</summary>
|
||||
public sealed record DrawingDimensionResult
|
||||
{
|
||||
/// <summary>Измеренное значение размера (мм).</summary>
|
||||
/// <summary>Измеренное значение размера: линейный/диаметральный/радиальный — мм, угловой — градусы.</summary>
|
||||
public required double Value { get; init; }
|
||||
|
||||
/// <summary>Номер вида, на котором поставлен размер.</summary>
|
||||
|
||||
@@ -184,6 +184,109 @@ public sealed class DrawingService
|
||||
public Task<int> GetViewDiametralDimensionCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.DiametralDimensions?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Поставить радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом <paramref name="radius"/>
|
||||
/// (ЛОКАЛЬНАЯ СК вида, мм) на виде. <paramref name="angleDeg"/> — направление выноски (градусы).
|
||||
/// Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида.
|
||||
/// </summary>
|
||||
public Task<DrawingDimensionResult> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Число радиальных размеров на виде (для проверки попадания в вид — они в
|
||||
/// ISymbols2DContainer, а не в IView.ObjectCount).</summary>
|
||||
public Task<int> GetViewRadialDimensionCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.RadialDimensions?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Поставить угловой размер между лучами (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) (ЛОКАЛЬНАЯ СК вида, мм)
|
||||
/// на виде. (x3,y3) — положение размерной дуги (задаёт её радиус = расстояние от вершины).
|
||||
/// <paramref name="angleType"/> выбирает, какой из двух углов измерять (min/max/more) — например
|
||||
/// 90° или 270° для перпендикуляра. Значение (градусы) измеряется автоматически. Возвращает угол (°) и номер вида.
|
||||
/// </summary>
|
||||
public Task<DrawingDimensionResult> 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; // градусы
|
||||
// Нулевой/некорректный угол — стороны коллинеарны (лучи сонаправлены → угол вырожден).
|
||||
if (value <= 0 || !double.IsFinite(value))
|
||||
throw new InvalidOperationException(
|
||||
$"Угловой размер измерил некорректное значение ({value}°) — стороны коллинеарны " +
|
||||
"(лучи (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) сонаправлены).");
|
||||
return new DrawingDimensionResult { Value = value, ViewNumber = view.Number };
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { dim.Delete(); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Число угловых размеров на виде (для проверки попадания в вид — они в
|
||||
/// ISymbols2DContainer, а не в IView.ObjectCount).</summary>
|
||||
public Task<int> GetViewAngularDimensionCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.AngleDimensions?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>Вид + его ISymbols2DContainer (QI от вида). Общий для линейных/диаметральных/… размеров.</summary>
|
||||
private (IView View, ISymbols2DContainer Symbols) RequireSymbols2DContainer(int viewNumber)
|
||||
{
|
||||
|
||||
@@ -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<string> 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<string> 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 положения размерной дуги (отстоит от вершины; угол выбирает angleType, не эта точка)")] 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.###}°.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ArgumentException>(() => AngleDimensionTypes.Parse("acute"));
|
||||
|
||||
[Fact]
|
||||
public void Parse_throws_on_null()
|
||||
=> Assert.Throws<ArgumentNullException>(() => AngleDimensionTypes.Parse(null!));
|
||||
|
||||
// ToKompas (→ ksAngleDimTypeEnum) намеренно НЕ покрыт unit-тестом: тестовый проект не ссылается
|
||||
// на Kompas6Constants (граница проекта, как у DimensionOrientations.ToKompas). Маппинг тривиален
|
||||
// и проверяется интеграцией (min → острый угол 90°, выбор сектора).
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Drawings;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: угловой размер на виде (API7 ISymbols2DContainer.AngleDimensions).</summary>
|
||||
[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<string> 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<string> 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 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()
|
||||
{
|
||||
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<ArgumentException>(() =>
|
||||
_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<InvalidOperationException>(() =>
|
||||
_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<InvalidOperationException>(() =>
|
||||
_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<InvalidOperationException>(() =>
|
||||
_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); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Drawings;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: радиальный размер (R) на виде (API7 ISymbols2DContainer.RadialDimensions).</summary>
|
||||
[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<string> 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<ArgumentOutOfRangeException>(() =>
|
||||
_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<InvalidOperationException>(() =>
|
||||
_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<InvalidOperationException>(() =>
|
||||
_drawing.AddRadialDimensionAsync(viewNumber: 999, xc: 0, yc: 0, radius: 10, angleDeg: 0));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user