Merge feat/drawing-associative-dimension: ассоциативная привязка размеров (инкремент 7)
Флаг associate у диаметрального/радиального — привязка к спроецированной окружности через BaseObject (значение из геометрии). 81 инструмент, 278 тестов зелёных.
This commit is contained in:
@@ -73,7 +73,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
```
|
||||
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции, конвертация, снимок, инспекция модели
|
||||
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов
|
||||
tests/Kompas.Mcp.Tests/ 267 тестов: unit + integration (integration требуют КОМПАС)
|
||||
tests/Kompas.Mcp.Tests/ 278 тестов: unit + integration (integration требуют КОМПАС)
|
||||
libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common)
|
||||
docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/)
|
||||
usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -220,3 +220,8 @@ stateful-сессии? Пока полагаемся на последовате
|
||||
`drawing_set_technical_requirements(text)` — технические требования документа (уровень документа, не вида). Паттерн API7: `(IDrawingDocument)doc.TechnicalDemand` → `td.Text.Str = text` → `td.Update()`. Перезаписывает прежние. Возвращает число строк.
|
||||
|
||||
Итог: **81 инструмент, 267 тестов (163 unit + 104 integration).** Нереализовано: ассоциативная привязка размеров и шероховатости к геометрии (`BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`).
|
||||
|
||||
### ✅ Ассоциативная привязка диаметрального/радиального к окружности — реализована (инкремент 7 вехи «2D-чертёж»)
|
||||
**Решено (частично).** Параметр `associate=true` добавлен к `drawing_add_diametral_dimension` и `drawing_add_radial_dimension`. Реализация (API7, `DrawingService`): новый класс `CircularObjectMatch` — отбирает окружность-кандидата в `IDrawingContainer.Circles` по центру (xc,yc) + радиусу с допуском 1 мм; бросает при отсутствии или неоднозначности (концентрические окружности различаются по радиусу). Хелпер `RequireViewContainers` получает одновременно `ISymbols2DContainer` и `IDrawingContainer` для вида. `_Circle` реализует `IDrawingObject`, поэтому `dim.BaseObject = circle` без дополнительного приведения. `associate=false` — прежнее «свободное» поведение без изменений. Подтверждено спайком: `BaseObject=circle` без задания Xc/Yc/Radius → `Valid`, `NominalValue` из геометрии (цилиндр R10: Ø=20 / R=10). +5 unit-тестов (`CircularObjectMatchTests`) + 6 интеграционных (`DrawingAssociativeDimensionTests`). Итог: **81 инструмент, 278 тестов (168 unit + 110 integration).**
|
||||
|
||||
Нереализовано (следующие инкременты): привязка к дуге (`IDrawingContainer.Arcs`), ассоциативный угловой размер (`BaseObject1/2`, отрезки), ассоциативная шероховатость (`IRough.BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`).
|
||||
|
||||
+9
-4
@@ -13,10 +13,15 @@
|
||||
Готово: стандартные виды, основная надпись (штамп), размеры (линейный/диаметральный/радиальный/угловой),
|
||||
текстовые обозначения (шероховатость, свободный текст, технические требования).
|
||||
|
||||
- ☐ **Ассоциативная привязка размеров и шероховатости к геометрии вида** (`BaseObject`/`BaseObject1/2`).
|
||||
Обозначение «прилипает» к рёбрам/окружностям вида и обновляется с моделью, а не задаётся координатами.
|
||||
Самый ценный следующий шаг («умный» чертёж). Требует спайка: перечисление геометрии вида
|
||||
(`IDrawingContainer`: LineSegments/Circles/Arcs), выбор объекта по точке/индексу, связывание.
|
||||
- ▣ **Ассоциативная привязка размеров и шероховатости к геометрии вида** (`BaseObject`/`BaseObject1/2`).
|
||||
Обозначение «прилипает» к геометрии вида и обновляется с моделью, а не задаётся координатами.
|
||||
- ✓ Диаметральный/радиальный → **окружность** (`Circles`): флаг `associate` (инкремент 7); поиск по
|
||||
центру+радиусу (`CircularObjectMatch`), значение Ø/R из геометрии.
|
||||
- ☐ Привязка к **дуге** (`Arcs`) — нужен свой спайк (`IArc` как `BaseObject`; дуги одного родителя
|
||||
неотличимы по центру+радиусу — нужен ключ по углу/точке).
|
||||
- ☐ **Угловой** → пара отрезков (`BaseObject1/2`, `LineSegments`).
|
||||
- ☐ **Шероховатость** → `IRough.BaseObject` (привязка к контуру/ребру).
|
||||
- ⛔/☐ **Линейный** — у `ILineDimension` нет `BaseObject`; нужен иной механизм (отложено).
|
||||
- ☐ **Рамка/формат листа** (A4/A3…, ориентация) — `ILayoutSheet`/`SheetFormat`. Быстрый фундаментальный
|
||||
шаг, вероятно без спайка. Делает чертёж «правильным» по ГОСТ.
|
||||
- ☐ **Выноски** (`Leaders`) — `ISymbols2DContainer.Leaders` (полки-выноски с текстом).
|
||||
|
||||
@@ -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+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements (81 инструмент)</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+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements+associate (81 инструмент)</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 · drawing_add_radial_dimension · drawing_add_angular_dimension · drawing_add_rough · drawing_add_text · drawing_set_technical_requirements · навык kompas-3d · 81 инструмент · 267 тестов</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 · drawing_add_rough · drawing_add_text · drawing_set_technical_requirements · associate (ассоциативная привязка диам./радиального) · навык kompas-3d · 81 инструмент · 278 тестов</span></div>
|
||||
<div class="bar"><i style="width:97%"></i></div>
|
||||
</div>
|
||||
|
||||
@@ -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) · <b>drawing_add_radial_dimension</b> (радиальный размер R, IRadialDimension, API7) · <b>drawing_add_angular_dimension</b> (угловой размер, IAngleDimension, angleType=min/max/more, API7) · <b>drawing_add_rough</b> (знак шероховатости, IRough/IRoughParams, signType=delete/without/none, API7) · <b>drawing_add_text</b> (свободный текст на виде, IDrawingContainer/IDrawingText, API7) · <b>drawing_set_technical_requirements</b> (тех. требования документа, IDrawingDocument/TechnicalDemand, API7)</span></li>
|
||||
<li><span class="mark wip">→</span><span>2D-чертёж: ассоциативная привязка размеров и шероховатости к геометрии (BaseObject), рамка/формат, выноски (Leaders), обозначения баз (Bases), допуски формы (Tolerances) · пакет 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, <b>associate=true</b> для ассоциативной привязки к окружности, API7) · <b>drawing_add_radial_dimension</b> (радиальный размер R, IRadialDimension, <b>associate=true</b> аналогично, API7) · <b>drawing_add_angular_dimension</b> (угловой размер, IAngleDimension, angleType=min/max/more, API7) · <b>drawing_add_rough</b> (знак шероховатости, IRough/IRoughParams, signType=delete/without/none, API7) · <b>drawing_add_text</b> (свободный текст на виде, IDrawingContainer/IDrawingText, API7) · <b>drawing_set_technical_requirements</b> (тех. требования документа, IDrawingDocument/TechnicalDemand, API7)</span></li>
|
||||
<li><span class="mark wip">→</span><span>2D-чертёж: привязка к дуге (Arcs), ассоциативный угловой размер, ассоциативная шероховатость (IRough.BaseObject), рамка/формат, выноски (Leaders), обозначения баз (Bases), допуски формы (Tolerances) · пакет 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">Итого 267 тестов зелёных (163 unit + 104 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 278 тестов зелёных (168 unit + 110 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>
|
||||
@@ -356,7 +356,8 @@
|
||||
<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" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 6 ✓</div><h4>Текстовые обозначения</h4><p><b>drawing_add_rough</b> (IRough/IRoughParams, signType=delete/without/none, API7) · <b>drawing_add_text</b> (IDrawingContainer/IDrawingText/IText, API7) · <b>drawing_set_technical_requirements</b> (IDrawingDocument/TechnicalDemand, уровень документа, API7) · DrawingAnnotationResult · RoughSignType enum · 81 инструмент · 267 тестов</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Ассоциативная привязка размеров и шероховатости к геометрии (BaseObject) · рамка/формат · выноски (Leaders) · обозначения баз (Bases) · допуски формы (Tolerances) · пакет 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-чертёж · инкремент 7 ✓</div><h4>Ассоциативная привязка к окружности</h4><p><b>associate=true</b> у drawing_add_diametral_dimension и drawing_add_radial_dimension · CircularObjectMatch (поиск по центру+радиусу, допуск 1 мм) · RequireViewContainers · dim.BaseObject=circle · значение из геометрии · 81 инструмент · 278 тестов</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Привязка к дуге (Arcs) · ассоциативный угловой размер · ассоциативная шероховатость (IRough.BaseObject) · рамка/формат · выноски (Leaders) · обозначения баз (Bases) · допуски формы (Tolerances) · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# Дизайн: ассоциативная привязка диаметрального и радиального размеров к геометрии вида (API7)
|
||||
|
||||
**Дата:** 2026-05-27
|
||||
**Статус:** дизайн согласован (вариант A — флаг `associate`), спайк проведён, ревью Codex спека учтено — к реализации
|
||||
|
||||
## Правки по ревью Codex (спек)
|
||||
|
||||
- **#3/#4** объём сужен до **окружностей** (`IDrawingContainer.Circles`): спайк подтвердил привязку
|
||||
только к окружности. Дуги (`Arcs`) — следующий инкремент со своим спайком (привязка `IArc` как
|
||||
`BaseObject` для диам./радиального не проверена; дуги одного родителя неотличимы по центру+радиусу).
|
||||
- **#1** вид резолвится один раз и приводится к обоим контейнерам: `ISymbols2DContainer` (создание
|
||||
размера) И `IDrawingContainer` (поиск окружности). Новый helper `RequireViewContainers(viewNumber)`
|
||||
→ `(IView, ISymbols2DContainer, IDrawingContainer)`.
|
||||
- **#2** весь блок после `…Dimensions.Add()` (присвоение `BaseObject`/`Angle`/`AutoNominalValue`,
|
||||
`Update`, `Valid`, read-back) — внутри того же `try/catch` с `Delete` при любом сбое (как свободный путь).
|
||||
- **#5** тест доказывает ассоциативность: ключ поиска `radius=10.3` (в допуске находит окружность R10)
|
||||
→ associate вернёт Ø=20 (из геометрии), свободный дал бы 2·10.3=20.6; `Value≈20` (не 20.6) доказывает
|
||||
чтение из геометрии. + диагностический read-back `BaseObject != null`.
|
||||
- **#6** обе ветки (свободная/associate) конвертируют `angle` через `DimensionAngles.ToRadians(angleDeg)`
|
||||
перед присвоением `dim.Angle`.
|
||||
- **#7** пост-инвариант значения (`value > 0 && double.IsFinite`) сохраняется и в associate-ветке (иначе откат).
|
||||
- **#8** регресс-тесты свободного пути (`associate=false`) для ОБОИХ — диаметрального и радиального.
|
||||
|
||||
## Цель
|
||||
|
||||
Веха 2D-ЧЕРТЁЖ, инкремент 7. Размеры пока «свободные» (значение из заданных координат). Ассоциативная
|
||||
привязка делает диаметральный/радиальный размер **связанным с проекцией геометрии**: значение (Ø/R)
|
||||
читается из спроецированной окружности/дуги вида через `BaseObject` и обновляется вместе с моделью.
|
||||
Первый шаг к «умному» чертежу.
|
||||
|
||||
## Объём (обоснован рефлексией interop)
|
||||
|
||||
Только **диаметральный** и **радиальный**, привязка только к **окружности** (`IDrawingContainer.Circles`)
|
||||
— у `IDiametralDimension`/`IRadialDimension` есть свойство `BaseObject`; `_Circle` реализует
|
||||
`IDrawingObject`, поэтому объект из коллекции присваивается `dim.BaseObject` (подтверждено спайком).
|
||||
У `ILineDimension` `BaseObject` НЕТ; угловой требует `BaseObject1/2`; привязка к дуге (`Arcs`) не
|
||||
проверена — всё это отдельные инкременты (вне объёма).
|
||||
|
||||
## Спайк: механизм подтверждён вживую (НЕ разучивать)
|
||||
|
||||
Спайк (`_SpikeAssocDim`, прогнан на реальном КОМПАС, затем удалён): цилиндр (окружность R10, выдавлен
|
||||
на 20) → стандартные виды. Один из видов содержит спроецированную окружность.
|
||||
|
||||
```
|
||||
IDrawingContainer dc = (IDrawingContainer)view; // QI от вида (как ISymbols2DContainer)
|
||||
ICircles circles = dc.Circles; // Count=1; circles[0] as ICircle → Xc=0,Yc=0,R=10
|
||||
IDiametralDimension dia = symbols.DiametralDimensions.Add();
|
||||
dia.BaseObject = circles[0]; // IDrawingObject; Xc/Yc/Radius НЕ задаём
|
||||
dia.Angle = π/4; // направление выноски (размещение)
|
||||
dia.Update(); // True, Valid=True
|
||||
double v = ((IDimensionText)dia).NominalValue; // == 20 — ДИАМЕТР, прочитан из геометрии!
|
||||
```
|
||||
|
||||
**Проверено:** `BaseObject=circle` без задания координат → `Update=True`, `Valid=True`,
|
||||
диаметральный `NominalValue=20` (=2·R геометрии), радиальный `NominalValue=10` (=R). `BaseObject`
|
||||
остаётся непустым после. Геометрия вида: `IDrawingContainer.Circles` (`ICircle`: `Xc/Yc/Radius`),
|
||||
`.Arcs` (`IArc`: `Xc/Yc/Radius/Angle1/Angle2`), `.LineSegments`. **Нет** «find by point» — выбор
|
||||
перебором коллекции.
|
||||
|
||||
## MCP-инструменты (расширение существующих, вариант A)
|
||||
|
||||
К `drawing_add_diametral_dimension` и `drawing_add_radial_dimension` добавляется параметр
|
||||
`associate=false`:
|
||||
|
||||
| `associate` | Поведение |
|
||||
|---|---|
|
||||
| `false` (по умолчанию) | Текущее «свободное»: `Xc/Yc/Radius` задаются напрямую (без изменений, обратная совместимость). |
|
||||
| `true` | `(xc,yc,radius)` — **ключ поиска**: найти в виде окружность/дугу с центром ≈ (xc,yc) и радиусом ≈ radius, привязать `BaseObject`; значение (Ø/R) читается из геометрии. |
|
||||
|
||||
Координаты ключа — view-local (= координаты модели при масштабе 1:1; при ином масштабе — масштабированные,
|
||||
как у прочих размеров). `angle` — направление выноски (°), как сейчас. `viewNumber` — как сейчас.
|
||||
|
||||
## Выбор объекта
|
||||
|
||||
В `IDrawingContainer` нет «find by point». Перебираем `Circles`, собираем кандидатов `(Xc, Yc, Radius)`,
|
||||
ищем подходящий по ключу `(xc, yc, radius)`:
|
||||
- кандидат подходит, если `dist(центр, ключ) ≤ tol` И `|R − radius| ≤ tol` (`tol = 1.0 мм`, константа);
|
||||
- ровно 1 подходящий → привязываем; 0 → ошибка «окружность не найдена рядом с (xc,yc) R≈radius»;
|
||||
>1 → ошибка «неоднозначно — уточните координаты/радиус».
|
||||
|
||||
Радиус в ключе снимает неоднозначность концентрических окружностей (цековка R5/R10 — ключ R10
|
||||
выберет только внешнюю). Логика отбора — чистая (unit-тест), перечисление COM — в сервисе.
|
||||
|
||||
## Архитектура
|
||||
|
||||
- `DrawingService.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angleDeg, associate, ct)` и
|
||||
`AddRadialDimensionAsync(..., associate, ct)` — добавлен `associate` (по умолчанию `false`). При `false` —
|
||||
текущая ветка без изменений. Обе ветки: `angleRad = DimensionAngles.ToRadians(angleDeg)`.
|
||||
- При `associate=true`: `RequireViewContainers(viewNumber)` → собрать кандидатов из `Circles`
|
||||
(`CollectCircles`) → `CircularObjectMatch.SelectMatchIndex(...)` → взять `IDrawingObject` по индексу →
|
||||
`symbols.{Diametral|Radial}Dimensions.Add()` → **внутри `try/catch`+`Delete`**: `dim.BaseObject = obj`,
|
||||
`dim.Angle = angleRad`, `AutoNominalValue=true` → `Update()` (FALSE→откат) → `Valid` (false→откат) →
|
||||
`value = NominalValue` (из геометрии); `value<=0 || !IsFinite` → откат → `DrawingDimensionResult`.
|
||||
- Новый helper `RequireViewContainers(viewNumber)` → `(IView View, ISymbols2DContainer Symbols,
|
||||
IDrawingContainer Drawing)` — резолвит вид один раз, оба QI с раздельной диагностикой (переиспользует
|
||||
`FindView`).
|
||||
- Новый файл `src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs`: чистый статический
|
||||
`record CircleCandidate(double Xc, double Yc, double Radius)` + `int SelectMatchIndex(IReadOnlyList<CircleCandidate>
|
||||
candidates, double keyX, double keyY, double keyR, double tol)` — возвращает индекс единственного
|
||||
совпадения; `InvalidOperationException` при 0 (нет) / >1 (неоднозначно).
|
||||
- Сбор кандидатов из COM (Circles в список с сохранением ссылки на `IDrawingObject` + геометрии) — в
|
||||
сервисе (private helper `CollectCircles(IDrawingContainer)` → `List<(IDrawingObject Obj, CircleCandidate Geom)>`).
|
||||
- `DrawingDimensionResult` переиспользуется (значение из геометрии). Свободный путь не трогаем.
|
||||
|
||||
## Валидация
|
||||
|
||||
- Оба пути: `RequireFiniteCoords(xc, yc, angleDeg)` + `RequirePositiveRadius(radius)` (есть). В
|
||||
associate-режиме radius — положительный ключ поиска.
|
||||
- `CircularObjectMatch.SelectMatchIndex` — чистая логика отбора (unit).
|
||||
|
||||
## Тестирование
|
||||
|
||||
### Unit (`CircularObjectMatchTests`)
|
||||
- Единственное совпадение по центру+радиусу → индекс.
|
||||
- Нет кандидатов в допуске → `InvalidOperationException` (нет).
|
||||
- Два совпадения в допуске → `InvalidOperationException` (неоднозначно).
|
||||
- Концентрические (один центр, разный R): ключ по R выбирает нужный (не неоднозначно).
|
||||
- Пустой список → `InvalidOperationException`.
|
||||
|
||||
### Integration (`DrawingAssociativeDimensionTests`; модель — цилиндр R10, как в спайке)
|
||||
1. **Диаметральный associative + доказательство геометрии**: ключ `(0,0,10.3)` (radius в допуске
|
||||
находит окружность R10) → `Value ≈ 20` (Ø из геометрии, НЕ 2·10.3=20.6 — доказывает чтение из
|
||||
геометрии); попадание в вид (`DiametralDimensions.Count` +1).
|
||||
2. **Радиальный associative**: `(0,0,10.3)` → `Value ≈ 10` (R из геометрии, не 10.3).
|
||||
3. **Нет окружности рядом**: ключ далеко от геометрии → `InvalidOperationException`; счётчик не вырос (откат).
|
||||
4. **Свободный путь не сломан** — диаметральный `associate=false` `(xc,yc,15)` → `Value ≈ 30`.
|
||||
5. **Свободный путь не сломан** — радиальный `associate=false` `(xc,yc,12)` → `Value ≈ 12` (регресс обоих).
|
||||
6. **Нет видов** (associate) → понятная ошибка.
|
||||
|
||||
## Дальнейшее (вне спека)
|
||||
- Ассоциативная привязка углового (`BaseObject1/2` — пара отрезков) и шероховатости (`IRough.BaseObject`);
|
||||
линейный — `BaseObject` отсутствует (нужен иной механизм). Рамка/формат листа; выноски/базы/допуски формы.
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace Kompas.Mcp.Core.Drawings;
|
||||
|
||||
/// <summary>Геометрия окружности-кандидата вида (центр + радиус, локальная СК вида, мм).</summary>
|
||||
public sealed record CircleCandidate(double Xc, double Yc, double Radius);
|
||||
|
||||
/// <summary>
|
||||
/// Чистый отбор окружности проекционного вида по ключу (центр + радиус) для ассоциативной привязки
|
||||
/// размера. Выделен из COM-перечисления ради unit-тестов.
|
||||
/// </summary>
|
||||
public static class CircularObjectMatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Индекс единственной окружности из <paramref name="candidates"/>, чей центр и радиус совпадают с
|
||||
/// ключом (<paramref name="keyX"/>,<paramref name="keyY"/>,<paramref name="keyR"/>) в пределах
|
||||
/// <paramref name="tol"/> (мм): <c>dist(центр,ключ) ≤ tol</c> И <c>|R − keyR| ≤ tol</c>.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Нет подходящих (0) или неоднозначно (>1).</exception>
|
||||
public static int SelectMatchIndex(IReadOnlyList<CircleCandidate> candidates,
|
||||
double keyX, double keyY, double keyR, double tol)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(candidates);
|
||||
|
||||
var matches = new List<int>();
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
var c = candidates[i];
|
||||
var dx = c.Xc - keyX;
|
||||
var dy = c.Yc - keyY;
|
||||
if (Math.Sqrt(dx * dx + dy * dy) <= tol && Math.Abs(c.Radius - keyR) <= tol)
|
||||
matches.Add(i);
|
||||
}
|
||||
|
||||
return matches.Count switch
|
||||
{
|
||||
1 => matches[0],
|
||||
0 => throw new InvalidOperationException(
|
||||
$"Окружность не найдена рядом с ({keyX},{keyY}) R≈{keyR} (допуск {tol} мм). " +
|
||||
"Проверьте координаты/радиус и наличие окружности на этом виде."),
|
||||
_ => throw new InvalidOperationException(
|
||||
$"Неоднозначный выбор окружности у ({keyX},{keyY}) R≈{keyR}: подходит {matches.Count}. " +
|
||||
"Уточните координаты/радиус."),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -137,27 +137,46 @@ public sealed class DrawingService
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.LineDimensions?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Поставить диаметральный размер (Ø) окружности с центром (xc,yc) и радиусом <paramref name="radius"/>
|
||||
/// (ЛОКАЛЬНАЯ СК вида, мм) на виде. <paramref name="angleDeg"/> — направление выноски (градусы).
|
||||
/// Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида.
|
||||
/// Поставить диаметральный размер (Ø) окружности на виде. <paramref name="angleDeg"/> — направление
|
||||
/// выноски (градусы). При <paramref name="associate"/>=false — свободный размер по (xc,yc,radius),
|
||||
/// значение = 2·radius. При <paramref name="associate"/>=true — (xc,yc,radius) служат ключом поиска
|
||||
/// спроецированной окружности (Circles), размер привязывается к ней (BaseObject), значение Ø читается
|
||||
/// из геометрии. Координаты — ЛОКАЛЬНАЯ СК вида (мм). Возвращает диаметр и номер вида.
|
||||
/// </summary>
|
||||
public Task<DrawingDimensionResult> AddDiametralDimensionAsync(int viewNumber, double xc, double yc,
|
||||
double radius, double angleDeg, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddDiametralDimensionCore(viewNumber, xc, yc, radius, angleDeg), ct);
|
||||
double radius, double angleDeg, bool associate = false, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddDiametralDimensionCore(viewNumber, xc, yc, radius, angleDeg, associate), ct);
|
||||
|
||||
private DrawingDimensionResult AddDiametralDimensionCore(int viewNumber, double xc, double yc,
|
||||
double radius, double angleDeg)
|
||||
double radius, double angleDeg, bool associate)
|
||||
{
|
||||
DrawingValidation.RequireFiniteCoords(xc, yc);
|
||||
DrawingValidation.RequirePositiveRadius(radius);
|
||||
var angleRad = DimensionAngles.ToRadians(angleDeg);
|
||||
|
||||
var (view, symbols) = RequireSymbols2DContainer(viewNumber);
|
||||
// Свободный путь берёт только ISymbols2DContainer (как раньше); IDrawingContainer (геометрия)
|
||||
// нужен лишь для ассоциативной привязки — не расширяем поверхность отказа свободного пути.
|
||||
IView view; ISymbols2DContainer symbols; IDrawingObject? baseObject = null;
|
||||
if (associate)
|
||||
{
|
||||
var c = RequireViewContainers(viewNumber);
|
||||
view = c.View; symbols = c.Symbols;
|
||||
baseObject = FindBaseCircle(c.Drawing, xc, yc, radius);
|
||||
}
|
||||
else
|
||||
{
|
||||
var c = RequireSymbols2DContainer(viewNumber);
|
||||
view = c.View; symbols = c.Symbols;
|
||||
}
|
||||
var dim = symbols.DiametralDimensions?.Add()
|
||||
?? throw new InvalidOperationException("DiametralDimensions.Add вернул null.");
|
||||
try
|
||||
{
|
||||
dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; dim.Angle = angleRad;
|
||||
if (baseObject != null)
|
||||
dim.BaseObject = baseObject; // ассоциативно: значение Ø читается из геометрии
|
||||
else
|
||||
{ dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; }
|
||||
dim.Angle = angleRad;
|
||||
// DimensionType (стиль размерной линии) не задаём — оставляем COM-дефолт (полярность bool
|
||||
// не подтверждена; дефолт даёт валидный размер с верным значением — проверено спайком).
|
||||
((IDimensionText)dim).AutoNominalValue = true;
|
||||
@@ -185,27 +204,46 @@ public sealed class DrawingService
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.DiametralDimensions?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Поставить радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом <paramref name="radius"/>
|
||||
/// (ЛОКАЛЬНАЯ СК вида, мм) на виде. <paramref name="angleDeg"/> — направление выноски (градусы).
|
||||
/// Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида.
|
||||
/// Поставить радиальный размер (R) окружности на виде. <paramref name="angleDeg"/> — направление
|
||||
/// выноски (градусы). При <paramref name="associate"/>=false — свободный размер по (xc,yc,radius),
|
||||
/// значение = radius. При <paramref name="associate"/>=true — (xc,yc,radius) служат ключом поиска
|
||||
/// спроецированной окружности (Circles), размер привязывается к ней (BaseObject), значение R читается
|
||||
/// из геометрии. Координаты — ЛОКАЛЬНАЯ СК вида (мм). Возвращает радиус и номер вида.
|
||||
/// </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);
|
||||
double radius, double angleDeg, bool associate = false, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddRadialDimensionCore(viewNumber, xc, yc, radius, angleDeg, associate), ct);
|
||||
|
||||
private DrawingDimensionResult AddRadialDimensionCore(int viewNumber, double xc, double yc,
|
||||
double radius, double angleDeg)
|
||||
double radius, double angleDeg, bool associate)
|
||||
{
|
||||
DrawingValidation.RequireFiniteCoords(xc, yc);
|
||||
DrawingValidation.RequirePositiveRadius(radius);
|
||||
var angleRad = DimensionAngles.ToRadians(angleDeg);
|
||||
|
||||
var (view, symbols) = RequireSymbols2DContainer(viewNumber);
|
||||
// Свободный путь берёт только ISymbols2DContainer (как раньше); IDrawingContainer (геометрия)
|
||||
// нужен лишь для ассоциативной привязки — не расширяем поверхность отказа свободного пути.
|
||||
IView view; ISymbols2DContainer symbols; IDrawingObject? baseObject = null;
|
||||
if (associate)
|
||||
{
|
||||
var c = RequireViewContainers(viewNumber);
|
||||
view = c.View; symbols = c.Symbols;
|
||||
baseObject = FindBaseCircle(c.Drawing, xc, yc, radius);
|
||||
}
|
||||
else
|
||||
{
|
||||
var c = RequireSymbols2DContainer(viewNumber);
|
||||
view = c.View; symbols = c.Symbols;
|
||||
}
|
||||
var dim = symbols.RadialDimensions?.Add()
|
||||
?? throw new InvalidOperationException("RadialDimensions.Add вернул null.");
|
||||
try
|
||||
{
|
||||
dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; dim.Angle = angleRad;
|
||||
if (baseObject != null)
|
||||
dim.BaseObject = baseObject; // ассоциативно: значение R читается из геометрии
|
||||
else
|
||||
{ dim.Xc = xc; dim.Yc = yc; dim.Radius = radius; }
|
||||
dim.Angle = angleRad;
|
||||
dim.DimensionType = true; // стиль R «от центра» (на величину не влияет — спайк)
|
||||
((IDimensionText)dim).AutoNominalValue = true;
|
||||
|
||||
@@ -429,6 +467,45 @@ public sealed class DrawingService
|
||||
return (view, container);
|
||||
}
|
||||
|
||||
/// <summary>Допуск совпадения окружности с ключом поиска при ассоциативной привязке (мм).</summary>
|
||||
private const double CircleMatchToleranceMm = 1.0;
|
||||
|
||||
/// <summary>Вид + оба контейнера (QI от одного вида): ISymbols2DContainer (создание размера) и
|
||||
/// IDrawingContainer (поиск геометрии для ассоциативной привязки).</summary>
|
||||
private (IView View, ISymbols2DContainer Symbols, IDrawingContainer Drawing) RequireViewContainers(int viewNumber)
|
||||
{
|
||||
var view = FindView(viewNumber);
|
||||
var symbols = view as ISymbols2DContainer
|
||||
?? throw new InvalidOperationException("Вид не приводится к ISymbols2DContainer.");
|
||||
var drawing = view as IDrawingContainer
|
||||
?? throw new InvalidOperationException("Вид не приводится к IDrawingContainer.");
|
||||
return (view, symbols, drawing);
|
||||
}
|
||||
|
||||
/// <summary>Найти окружность вида по ключу (центр + радиус) и вернуть её как IDrawingObject
|
||||
/// (для <c>dim.BaseObject</c>). Бросает при отсутствии/неоднозначности.</summary>
|
||||
private static IDrawingObject FindBaseCircle(IDrawingContainer drawing, double xc, double yc, double radius)
|
||||
{
|
||||
var items = CollectCircles(drawing);
|
||||
var index = CircularObjectMatch.SelectMatchIndex(
|
||||
items.Select(i => i.Geom).ToList(), xc, yc, radius, CircleMatchToleranceMm);
|
||||
return items[index].Obj;
|
||||
}
|
||||
|
||||
/// <summary>Собрать окружности вида (IDrawingContainer.Circles) с их геометрией и COM-ссылкой.</summary>
|
||||
private static List<(IDrawingObject Obj, CircleCandidate Geom)> CollectCircles(IDrawingContainer drawing)
|
||||
{
|
||||
var result = new List<(IDrawingObject, CircleCandidate)>();
|
||||
var circles = drawing.Circles;
|
||||
if (circles == null) return result;
|
||||
for (var i = 0; i < circles.Count; i++)
|
||||
{
|
||||
if (circles[i] is not ICircle c) continue;
|
||||
result.Add(((IDrawingObject)c, new CircleCandidate(c.Xc, c.Yc, c.Radius)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>Вид + его ISymbols2DContainer (QI от вида). Общий для линейных/диаметральных/… размеров.</summary>
|
||||
private (IView View, ISymbols2DContainer Symbols) RequireSymbols2DContainer(int viewNumber)
|
||||
{
|
||||
|
||||
@@ -71,35 +71,45 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
|
||||
"задаётся центром (xc,yc) и радиусом radius в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты " +
|
||||
"модели на плоскости вида) — задайте параметры реальной окружности вида. angle — направление выноски " +
|
||||
"в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). " +
|
||||
"Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида.")]
|
||||
"associate=false: свободный размер (значение = 2·radius). associate=true: АССОЦИАТИВНО — (xc,yc,radius) " +
|
||||
"используются как ключ для поиска спроецированной окружности на виде; размер привязывается к ней " +
|
||||
"(BaseObject) и значение Ø читается из геометрии и обновляется с моделью. Значение измеряется " +
|
||||
"автоматически. Возвращает диаметр и номер вида.")]
|
||||
public async Task<string> AddDiametralDimension(
|
||||
[Description("X центра окружности (СК вида, мм)")] double xc,
|
||||
[Description("Y центра окружности")] double yc,
|
||||
[Description("Радиус окружности, мм")] double radius,
|
||||
[Description("Направление выноски, градусы")] double angle = 0,
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0,
|
||||
[Description("true = привязать к окружности вида (значение из геометрии); false = свободный размер")] bool associate = false)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var r = await drawing.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angle);
|
||||
return $"Диаметральный размер поставлен на виде {r.ViewNumber}, Ø{r.Value:0.###} мм.";
|
||||
var r = await drawing.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angle, associate);
|
||||
var mode = associate ? " (ассоциативно)" : "";
|
||||
return $"Диаметральный размер поставлен на виде {r.ViewNumber}, Ø{r.Value:0.###} мм{mode}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_add_radial_dimension")]
|
||||
[Description("Поставить радиальный размер (R) окружности/дуги на виде активного чертежа. Окружность " +
|
||||
[Description("Поставить радиальный размер (R) окружности на виде активного чертежа. Окружность " +
|
||||
"задаётся центром (xc,yc) и радиусом radius в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты " +
|
||||
"модели на плоскости вида) — задайте параметры реальной окружности вида. angle — направление выноски " +
|
||||
"в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). " +
|
||||
"Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида.")]
|
||||
"associate=false: свободный размер (значение = radius). associate=true: АССОЦИАТИВНО — (xc,yc,radius) " +
|
||||
"используются как ключ для поиска спроецированной окружности на виде; размер привязывается к ней " +
|
||||
"(BaseObject) и значение R читается из геометрии и обновляется с моделью. Значение измеряется " +
|
||||
"автоматически. Возвращает радиус и номер вида.")]
|
||||
public async Task<string> AddRadialDimension(
|
||||
[Description("X центра окружности (СК вида, мм)")] double xc,
|
||||
[Description("Y центра окружности")] double yc,
|
||||
[Description("Радиус окружности, мм")] double radius,
|
||||
[Description("Направление выноски, градусы")] double angle = 0,
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0,
|
||||
[Description("true = привязать к окружности вида (значение из геометрии); false = свободный размер")] bool associate = false)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var r = await drawing.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angle);
|
||||
return $"Радиальный размер поставлен на виде {r.ViewNumber}, R{r.Value:0.###} мм.";
|
||||
var r = await drawing.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angle, associate);
|
||||
var mode = associate ? " (ассоциативно)" : "";
|
||||
return $"Радиальный размер поставлен на виде {r.ViewNumber}, R{r.Value:0.###} мм{mode}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_add_angular_dimension")]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using Kompas.Mcp.Core.Drawings;
|
||||
|
||||
namespace Kompas.Mcp.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class CircularObjectMatchTests
|
||||
{
|
||||
private const double Tol = 1.0;
|
||||
|
||||
private static IReadOnlyList<CircleCandidate> Candidates(params (double x, double y, double r)[] items)
|
||||
=> items.Select(i => new CircleCandidate(i.x, i.y, i.r)).ToList();
|
||||
|
||||
[Fact]
|
||||
public void SelectMatchIndex_returns_unique_match()
|
||||
{
|
||||
var c = Candidates((0, 0, 10), (50, 20, 5));
|
||||
// ключ с допустимым отклонением центра и радиуса
|
||||
Assert.Equal(0, CircularObjectMatch.SelectMatchIndex(c, 0.3, -0.2, 10.3, Tol));
|
||||
Assert.Equal(1, CircularObjectMatch.SelectMatchIndex(c, 50, 20, 5, Tol));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMatchIndex_concentric_disambiguated_by_radius()
|
||||
{
|
||||
// концентрические R5 и R10 (цековка): ключ R10 выбирает внешнюю, не неоднозначно
|
||||
var c = Candidates((0, 0, 5), (0, 0, 10));
|
||||
Assert.Equal(1, CircularObjectMatch.SelectMatchIndex(c, 0, 0, 10, Tol));
|
||||
Assert.Equal(0, CircularObjectMatch.SelectMatchIndex(c, 0, 0, 5, Tol));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMatchIndex_throws_when_none_in_tolerance()
|
||||
{
|
||||
var c = Candidates((0, 0, 10));
|
||||
// центр далеко
|
||||
Assert.Throws<InvalidOperationException>(() => CircularObjectMatch.SelectMatchIndex(c, 100, 100, 10, Tol));
|
||||
// радиус далеко
|
||||
Assert.Throws<InvalidOperationException>(() => CircularObjectMatch.SelectMatchIndex(c, 0, 0, 25, Tol));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMatchIndex_throws_when_ambiguous()
|
||||
{
|
||||
// две почти совпадающие окружности в допуске
|
||||
var c = Candidates((0, 0, 10), (0.2, 0.2, 10.2));
|
||||
Assert.Throws<InvalidOperationException>(() => CircularObjectMatch.SelectMatchIndex(c, 0, 0, 10, Tol));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMatchIndex_throws_on_empty()
|
||||
=> Assert.Throws<InvalidOperationException>(
|
||||
() => CircularObjectMatch.SelectMatchIndex(Candidates(), 0, 0, 10, Tol));
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Drawings;
|
||||
using Kompas.Mcp.Core.Modeling;
|
||||
|
||||
namespace Kompas.Mcp.Tests.Integration;
|
||||
|
||||
/// <summary>Интеграция: ассоциативная привязка диаметрального/радиального размера к окружности вида
|
||||
/// (BaseObject). Модель — цилиндр R10 (вид проецирует окружность).</summary>
|
||||
[Trait("Category", "Integration")]
|
||||
[Collection(KompasCollection.Name)]
|
||||
public sealed class DrawingAssociativeDimensionTests : IntegrationTestBase
|
||||
{
|
||||
private readonly DocumentService _docs;
|
||||
private readonly PartModeler _modeler;
|
||||
private readonly DrawingService _drawing;
|
||||
|
||||
public DrawingAssociativeDimensionTests(KompasFixture fx) : base(fx)
|
||||
{
|
||||
_docs = new DocumentService(fx.Session, fx.Dispatcher);
|
||||
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
|
||||
_drawing = new DrawingService(fx.Session, fx.Dispatcher);
|
||||
}
|
||||
|
||||
private async Task<string> BuildAndSaveCylinderAsync()
|
||||
{
|
||||
var path = TestPaths.NewFile(".m3d");
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddCircleAsync(s, 0, 0, 10);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 20);
|
||||
await _modeler.RebuildAsync();
|
||||
await _docs.SaveAsAsync(path);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
return path;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Diametral_associative_reads_diameter_from_geometry()
|
||||
{
|
||||
var partFile = await BuildAndSaveCylinderAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var target = views.ViewNumbers[0]; // вид с окружностью (спайк)
|
||||
|
||||
var before = await _drawing.GetViewDiametralDimensionCountAsync(target);
|
||||
// Ключ radius=10.3 (в допуске 1.0 находит окружность R10). Ассоциативно → Ø=20 из геометрии,
|
||||
// а НЕ 2·10.3=20.6 → доказывает чтение значения из геометрии, а не из входа.
|
||||
var r = await _drawing.AddDiametralDimensionAsync(target, xc: 0, yc: 0, radius: 10.3,
|
||||
angleDeg: 45, associate: true);
|
||||
var after = await _drawing.GetViewDiametralDimensionCountAsync(target);
|
||||
|
||||
Assert.Equal(target, r.ViewNumber);
|
||||
Assert.InRange(r.Value, 19.9, 20.1); // Ø=20 из геометрии (не 20.6)
|
||||
Assert.Equal(before + 1, after);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Radial_associative_reads_radius_from_geometry()
|
||||
{
|
||||
var partFile = await BuildAndSaveCylinderAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var target = views.ViewNumbers[0];
|
||||
var r = await _drawing.AddRadialDimensionAsync(target, xc: 0, yc: 0, radius: 10.3,
|
||||
angleDeg: -45, associate: true);
|
||||
Assert.InRange(r.Value, 9.9, 10.1); // R=10 из геометрии (не 10.3)
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Associative_no_circle_near_throws_and_no_orphan()
|
||||
{
|
||||
var partFile = await BuildAndSaveCylinderAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var target = views.ViewNumbers[0];
|
||||
var before = await _drawing.GetViewDiametralDimensionCountAsync(target);
|
||||
// Ключ далеко от любой окружности → отбор бросит до Add (размер не создаётся).
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_drawing.AddDiametralDimensionAsync(target, xc: 1000, yc: 1000, radius: 5,
|
||||
angleDeg: 0, associate: true));
|
||||
var after = await _drawing.GetViewDiametralDimensionCountAsync(target);
|
||||
Assert.Equal(before, after); // нет «осиротевшего» размера
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Free_diametral_path_unchanged()
|
||||
{
|
||||
var partFile = await BuildAndSaveCylinderAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
// associate=false (по умолчанию) — свободный размер: значение = 2·radius.
|
||||
var r = await _drawing.AddDiametralDimensionAsync(views.ViewNumbers[0], xc: 0, yc: 0,
|
||||
radius: 15, angleDeg: 0, associate: false);
|
||||
Assert.InRange(r.Value, 29.9, 30.1);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Free_radial_path_unchanged()
|
||||
{
|
||||
var partFile = await BuildAndSaveCylinderAsync();
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
|
||||
var r = await _drawing.AddRadialDimensionAsync(views.ViewNumbers[0], xc: 0, yc: 0,
|
||||
radius: 12, angleDeg: 0, associate: false);
|
||||
Assert.InRange(r.Value, 11.9, 12.1);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Associative_no_views_throws()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Drawing);
|
||||
try
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_drawing.AddDiametralDimensionAsync(0, xc: 0, yc: 0, radius: 10, angleDeg: 0, associate: true));
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user