Compare commits

...

4 Commits

Author SHA1 Message Date
mikhail 16406a34be Merge feat/drawing-leader: линия-выноска с текстом (инкремент 9)
drawing_add_leader через ISymbols2DContainer.Leaders + IBranchs. 83 инструмента,
331 тест зелёный. Ревью: Codex + pi/glm-5.1 + pi/kimi-k2.6.
2026-05-28 00:33:33 +03:00
mikhail 5d51b9e1af docs: выноска — TODO, README, ARCHITECTURE, OPEN_QUESTIONS, presentation, CLAUDE.md
Инкремент 9. 83 инструмента, 331 тест. + заметка о ревью реализации в спеке
(Codex + pi/glm-5.1 + pi/kimi-k2.6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:33:33 +03:00
mikhail beaa220510 review(drawing): правки по ревью реализации (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- проверка возврата AddBranchByPoint/SetBranchTextPosition (bool) — ранняя диагностика
  вместо позднего RPC_E_SERVERFAULT (Codex, glm)
- RU-алиасы в сообщении ShelfDirections.Parse и в описании инструмента (glm, kimi)
- интеграционный тест направления полки параметризован Right/Left/Up/Down — покрывает
  всю поверхность ToKompas на реальном COM (glm, kimi)
- отклонено (обоснование): unit ToKompas (граница проекта), e2e tool-обёртки (тонкая),
  angleDeg (нет Angle у ILeader), default text у AddText (вне объёма)

331 тест зелёный, сборка Release чистая.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:28:04 +03:00
mikhail f89ad951ae feat(drawing): линия-выноска с текстом (drawing_add_leader)
Выноска на виде через API7 ISymbols2DContainer.Leaders. Стрелка указывает в (x,y),
текст на полке у (textX,textY). shelfDirection: auto|right|left|up|down.

- DrawingService.AddLeaderAsync + GetViewLeaderCountAsync
- ShelfDirection (enum + Parse/ToKompas), переиспользует DrawingAnnotationResult
- 13 unit + 7 интеграционных тестов (всего 328 зелёных), сборка Release чистая

КЛЮЧ (спайк): новая выноска имеет 0 ответвлений — сначала (IBranchs)bl.AddBranchByPoint(0,x,y)
(остриё), ИНАЧЕ КОМПАС падает RPC_E_SERVERFAULT; затем SetBranchTextPosition (полка),
(ILeader)bl.TextOnShelf.Str (текст), Update. Ревью спека Codex+pi/glm-5.1+pi/kimi-k2.6 учтено
(QI as?? throw, read-back ?? empty, RequireDistinctPoints, RU-алиасы, критический порядок).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 00:18:08 +03:00
12 changed files with 460 additions and 19 deletions
+4 -3
View File
@@ -24,12 +24,12 @@ The base is committed to the repo (canonical) — no regeneration step. It cover
## Current state
**Status:** v1 + v2 + STEP/assembly + direct B-rep edit + structural inspection + 2D drawings — implemented and working (full sketch→feature→inspect→STEP round-trip + `move_face` + `describe_model` validated end-to-end).
Stack: **.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**. **82 MCP tools, 308 tests green (188 unit + 120 integration).**
Stack: **.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**. **83 MCP tools, 331 tests green (201 unit + 130 integration).**
Where to look (single source of truth — do **not** duplicate these lists here):
- **Full tool catalog** (by group, all 82) → [`README.md`](README.md) §«Инструменты».
- **Full tool catalog** (by group, all 83) → [`README.md`](README.md) §«Инструменты».
- **Roadmap / what's already done** → [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §10.
- **Backlog / what's left** → [`docs/TODO.md`](docs/TODO.md) (canonical; top of «2D-ЧЕРТЁЖ» = next priority. Associative diametral/radial→circle binding (`associate` flag) and sheet format/orientation (`drawing_set_sheet_format`) done — next: arc/angular/rough binding, leaders, bases, tolerances; plus extra mate types, package E continuation).
- **Backlog / what's left** → [`docs/TODO.md`](docs/TODO.md) (canonical; top of «2D-ЧЕРТЁЖ» = next priority. Associative diametral/radial→circle binding (`associate` flag), sheet format (`drawing_set_sheet_format`), leaders (`drawing_add_leader`) done — next: bases, tolerances, arc/angular/rough binding; plus extra mate types, package E continuation).
- **Design decisions / caveats** → [`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). Known caveat («Ревью v2»): boss/cut direction on a selected face depends on face-normal orientation — `forward` may need flipping; the agent picks via snapshot or `describe_face` normal.
- **Verified COM call-chains & gotchas** → «Key implementation facts» below + memory files (`kompas-*-api*.md`).
@@ -88,6 +88,7 @@ dotnet run --project src/Kompas.Mcp.Host # start the MCP server (stdio)
- `drawing_add_text`**text lives in `IDrawingContainer`, NOT `ISymbols2DContainer`, and NOT in `IView.ObjectCount`**: `(IDrawingContainer)view.DrawingTexts.Add()`/`IDrawingText` (`X/Y/Angle`) + `(IText)dt.Str` = text; count via `DrawingTexts.Count`. Do NOT set `Height` (that's block height, not font).
- `drawing_set_technical_requirements`**document-level**: `(IDrawingDocument)doc.TechnicalDemand`/`ITechnicalDemand`: `td.Text.Str = text``td.Update()` (`IsCreated` False→True on first set; overwrites; `\n` multiline).
- `DrawingAnnotationResult{Value(read-back), ViewNumber}` is the shared result for rough/text.
- `drawing_add_leader``ISymbols2DContainer.Leaders.Add(ksDrLeader)``IBaseLeader`. **КРИТИЧЕСКИЙ ПОРЯДОК (спайк):** новая выноска имеет 0 ответвлений → `(IBranchs)bl.AddBranchByPoint(0,x,y)` (остриё) ДО `SetBranchTextPosition(textX,textY)`/`Update`, иначе КОМПАС падает `RPC_E_SERVERFAULT`. Текст — `(ILeader)bl.TextOnShelf.Str`; `ShelfDirection` (enum `ShelfDirection{Auto,Right,Left,Up,Down}`, Auto = не задавать). `AddBranchByPoint`/`SetBranchTextPosition` возвращают bool — проверять. Результат — общий `DrawingAnnotationResult`.
- `drawing_set_sheet_format``RequireLayoutSheet(n).Format` (`ISheetFormat`): `Format`=`ksDocumentFormatEnum` (`PaperFormat{A0..A5,User}`+`PaperFormats.Parse/ToKompas/FromKompas`), standard → set `VerticalOrientation=!landscape` (W/H auto-computed from enum+orientation, e.g. A3 landscape→420×297); **User → set `FormatWidth/Height` only, КОМПАС auto-derives `VerticalOrientation` from W/H and ignores the flag, never swaps W/H (spike)**`sheet.Update()`. New drawing defaults A4 portrait. `ValidateFormatDimensions`: User needs W/H>0, standard forbids W/H (must be 0).
- **Richer sketch primitives (API5 `ksDocument2D` wrappers)**: `ksArcBy3Points`; `ksArcByAngle` (centre/radius/start-end angles in degrees + counterClockwise flag); `ksEllipse` via `ksEllipseParam` (`KompasObject.GetParamStruct(ko_EllipseParam=22)`; **property names are `A`/`B` UPPERCASE in the interop**); `ksRegularPolygon` via `ksRegularPolygonParam` (`ko_RegularPolygonParam=92`; `describe = !inscribed`); NURBS spline via `ksNurbs(order=4)` + `ksNurbsPoint` loop + `ksEndObj`; `ksPoint`. Param structs released after use. `PartModeler` is partial: `.cs` (registry/helpers/`NewParam<T>`/reset), `.Sketch.cs` (2D primitives), `.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` (`Core/Modeling`) holds enum maps + validators (`RequirePositive`, `RequireVertexCount`, `RequirePoints`, `RequireMin`). Point lists use `record SketchPoint(X, Y)` (JSON names `x`/`y`).
+3 -3
View File
@@ -41,7 +41,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
}
```
## Инструменты (82 инструмента)
## Инструменты (83 инструмента)
| Группа | Инструменты |
|---|---|
@@ -56,7 +56,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_add_radial_dimension`, `drawing_add_angular_dimension`, `drawing_add_rough`, `drawing_add_text`, `drawing_set_technical_requirements`, `drawing_set_sheet_format` |
| 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`, `drawing_add_rough`, `drawing_add_text`, `drawing_set_technical_requirements`, `drawing_set_sheet_format`, `drawing_add_leader` |
| Validation | `validate_part` |
### Типовой сценарий
@@ -75,7 +75,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/ 308 тестов: unit + integration (integration требуют КОМПАС)
tests/Kompas.Mcp.Tests/ 331 тест: unit + integration (integration требуют КОМПАС)
libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common)
docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/)
usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d
+6 -5
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -230,3 +230,8 @@ stateful-сессии? Пока полагаемся на последовате
**Решено.** Новый инструмент `drawing_set_sheet_format(format, landscape, width?, height?, sheetNumber=1)` — задаёт формат и ориентацию активного листа чертежа. Паттерн API7: `doc2d.LayoutSheets.ItemByNumber[sheetNumber]``ILayoutSheet.Format` (`ISheetFormat`: `Format=ksDocumentFormatEnum`, `VerticalOrientation=!landscape`, `FormatWidth/Height` только для `format=user`) → `sheet.Update()`. Дефолтный лист нового чертежа — A4 книжный. Новые типы: enum `PaperFormat {A0,A1,A2,A3,A4,A5,User}` + `PaperFormats.Parse/ToKompas/FromKompas`; record `SheetFormatResult`; валидатор `DrawingValidation.ValidateFormatDimensions`; хелпер `RequireLayoutSheet`. Ключевая тонкость: для `format=user` КОМПАС выводит `VerticalOrientation` из соотношения сторон (`height>width`), флаг `landscape` в этом случае игнорируется — результат читается read-back из COM. Для стандартных форматов `width`/`height` запрещены (должны быть 0). +20 unit-тестов (`PaperFormats.Parse`, `ValidateFormatDimensions`) + 10 интеграционных (`DrawingSheetFormatTests`). Итог: **82 инструмента, 308 тестов (188 unit + 120 integration).**
Нереализовано: рамка по ГОСТ-стилю (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов.
### ✅ Линия-выноска с текстом — реализована (инкремент 9 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_add_leader(x,y, textX,textY, text, shelfDirection, viewNumber)` — ставит линию-выноску с надписью на полке на виде активного чертежа. Паттерн API7: `ISymbols2DContainer.Leaders.Add(ksDrLeader)``IBaseLeader``(IBranchs)bl.AddBranchByPoint(0,x,y)` (остриё, ответвление ответвлений — обязательно до `Update`, иначе `RPC_E_SERVERFAULT`) → `SetBranchTextPosition(textX,textY)``(ILeader)bl.TextOnShelf.Str = text` → опционально `ShelfDirection``Update()``Valid`. Новый enum `ShelfDirection {Auto,Right,Left,Up,Down}` + `ShelfDirections.Parse/ToKompas` в `src/Kompas.Mcp.Core/Drawings/ShelfDirection.cs`; переиспользует `DrawingAnnotationResult { Value, ViewNumber }`. +13 unit-тестов (`ShelfDirections.Parse`) + 10 интеграционных (`DrawingLeaderTests`, включая параметризацию направлений полки). Итог: **83 инструмента, 331 тест (201 unit + 130 integration).**
Нереализовано: обозначения баз (`Bases`), допуски формы (`Tolerances`); привязка к дуге (`Arcs`), ассоциативный угловой размер, ассоциативная шероховатость (`IRough.BaseObject`); рамка по ГОСТ-стилю; специальные выноски (позиция, клеймо, маркер).
+3 -1
View File
@@ -25,7 +25,9 @@
-**Формат/ориентация листа** (A0A5/user, книжная/альбомная) — `drawing_set_sheet_format`
(инкремент 8); `ILayoutSheet.Format`/`ISheetFormat`. Остаётся: ☐ рамка/основная надпись по
конкретному ГОСТ-стилю оформления (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов.
- **Выноски** (`Leaders`) — `ISymbols2DContainer.Leaders` (полки-выноски с текстом).
- **Выноски** (`Leaders`) — `drawing_add_leader` (инкремент 9): остриё + полка с текстом,
`shelfDirection`. Остаётся: ☐ многострочный/под-полкой текст (`TextUnderShelf`), несколько
ответвлений, привязка к геометрии (`IBranchs.SetBaseObject`), спец. выноски (позиция/клеймо/маркер).
-**Обозначения баз** (`Bases`) — `ISymbols2DContainer.Bases`.
-**Допуски формы и расположения** (`Tolerances`) — `ISymbols2DContainer.Tolerances` (обычно в связке с базой).
- ☐ (опц.) **Доп. виды**: разрез/сечение (`CutLines`), выносной элемент (`RemoteElements`), местный вид.
+8 -7
View File
@@ -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+associate+drawing_set_sheet_format (82 инструмента)</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+drawing_set_sheet_format+drawing_add_leader (83 инструмента)</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 · associate (ассоциативная привязка диам./радиального) · drawing_set_sheet_format (формат листа) · навык kompas-3d · 82 инструмента · 308 тестов</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 (ассоциативная привязка диам./радиального) · drawing_set_sheet_format (формат листа) · drawing_add_leader (выноска) · навык kompas-3d · 83 инструмента · 331 тест</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">82 инструмента, отдаются по MCP-протоколу</p>
<p class="meta">83 инструмента, отдаются по 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, <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) · <b>drawing_set_sheet_format</b> (формат и ориентация листа, ILayoutSheet/ISheetFormat, PaperFormat A0A5/user, API7)</span></li>
<li><span class="mark wip"></span><span>2D-чертёж: рамка/основная надпись по ГОСТ-стилю (LayoutLibraryFileName/LayoutStyleNumber), несколько листов · привязка к дуге (Arcs), ассоциативный угловой размер, ассоциативная шероховатость (IRough.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) · <b>drawing_set_sheet_format</b> (формат и ориентация листа, ILayoutSheet/ISheetFormat, PaperFormat A0A5/user, API7) · <b>drawing_add_leader</b> (линия-выноска с текстом, ISymbols2DContainer/IBaseLeader/IBranchs/ILeader, ShelfDirection enum, API7)</span></li>
<li><span class="mark wip"></span><span>2D-чертёж: рамка/основная надпись по ГОСТ-стилю (LayoutLibraryFileName/LayoutStyleNumber), несколько листов · привязка к дуге (Arcs), ассоциативный угловой размер, ассоциативная шероховатость (IRough.BaseObject) · обозначения баз (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">Итого 308 тестов зелёных (188 unit + 120 integration)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Итого 331 тест зелёных (201 unit + 130 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>
@@ -358,7 +358,8 @@
<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" 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" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 8 ✓</div><h4>Формат и ориентация листа</h4><p><b>drawing_set_sheet_format</b> (ILayoutSheet.Format / ISheetFormat, ksDocumentFormatEnum, A0A5/user, VerticalOrientation) · PaperFormat enum · SheetFormatResult · ValidateFormatDimensions · 82 инструмента · 308 тестов</p></div>
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Рамка/надпись по ГОСТ-стилю (LayoutLibraryFileName) · несколько листов · привязка к дуге (Arcs) · ассоциативный угловой размер · ассоциативная шероховатость (IRough.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-чертёж · инкремент 9 ✓</div><h4>Линия-выноска</h4><p><b>drawing_add_leader</b> (ISymbols2DContainer.Leaders.Add / IBaseLeader / IBranchs.AddBranchByPoint / ILeader.TextOnShelf, ShelfDirection enum auto/right/left/up/down, API7) · DrawingAnnotationResult · 83 инструмента · 331 тест</p></div>
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Рамка/надпись по ГОСТ-стилю (LayoutLibraryFileName) · несколько листов · привязка к дуге (Arcs) · ассоциативный угловой размер · ассоциативная шероховатость (IRough.BaseObject) · обозначения баз (Bases) · допуски формы (Tolerances) · специальные выноски (позиция/клеймо/маркер) · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API</p></div>
</div>
</div>
</section>
@@ -0,0 +1,119 @@
# Дизайн: линия-выноска с текстом (drawing_add_leader) через API7
**Дата:** 2026-05-28
**Статус:** дизайн согласован, спайк проведён, ревью Codex + pi/glm-5.1 + pi/kimi-k2.6 учтено — к реализации
## Правки по ревью реализации (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- Проверка возврата `AddBranchByPoint`/`SetBranchTextPosition` (оба `bool`) — ранняя диагностика
вместо позднего `RPC_E_SERVERFAULT` (Codex, glm).
- RU-алиасы в сообщении `ShelfDirections.Parse` и в `[Description]` инструмента (glm, kimi).
- Интеграционный тест направления полки параметризован `Right/Left/Up/Down` — покрывает всю
поверхность `ToKompas` на реальном COM (glm, kimi).
- Отклонено (обоснование): unit-тест `ToKompas` (граница проекта — тест не ссылается на
`Kompas6Constants`); e2e-тест tool-обёртки (тонкая, `Parse`+сервис покрыты); `angleDeg`
(у `ILeader`/`IBaseLeader` нет свойства Angle — ориентация геометрическая); выравнивание default
`text` у `AddText` (вне объёма; required-контракт `AddLeader` корректнее).
## Правки по ревью спека (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- **Сигнатура** `DrawingValidation.RequireNonEmptyText(text, nameof(text))` (двухаргументный — как есть).
- **QI с диагностикой:** `bl as IBranchs ?? throw`, `bl as ILeader ?? throw` (не прямой cast); read-back
`leader.TextOnShelf?.Str ?? string.Empty`; двухшаговый null-check `Leaders` (коллекция) и `Add()`
как `AddRoughCore`.
- **`ShelfDirections.ToKompas(Auto)` бросает `ArgumentOutOfRangeException`** (Auto обрабатывается в сервисе
ДО вызова — «не задавать ShelfDirection»; прямой вызов для Auto — ошибка). RU-алиасы `Parse`:
`авто/вправо/влево/вверх/вниз`.
- **Вырожденная выноска:** `RequireDistinctPoints(x, y, textX, textY)` (остриё ≠ полка; есть валидатор).
- **Критический порядок** (комментарий в коде): `AddBranchByPoint` ДО `SetBranchTextPosition`/`Update`
(иначе RPC_E_SERVERFAULT — выноска без ветвей).
- **Тесты:** пустой текст — `""` И `" "`; `viewNumber=0` резолвится в первый вид; путь `shelfDirection=auto`
(ShelfDirection не задаётся). XML-`<summary>` членов `ShelfDirection` — «в ЛОКАЛЬНОЙ СК вида».
- Нейминг COM-объекта — `baseLeader` (не `bl`). `GetViewLeaderCountAsync` считает ВСЕ подтипы выносок
(для v1 ок; фильтр — при добавлении ksDrPosLeader/… — в «Дальнейшее»).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 9. Поставить на виде **линию-выноску** (leader) с текстовой надписью на полке —
поясняющее обозначение, указывающее стрелкой на элемент (примечание, материал детали, и т.п.).
## Спайк: последовательность подтверждена вживую (НЕ разучивать)
Спайк (`_SpikeLeader`, прогнан на реальном КОМПАС, затем удалён). **Ключ:** только что созданная
выноска имеет **0 ответвлений** — любые операции над ответвлением (`SetSignPosition`) и `Update` на
выноске без ветвей падают с `RPC_E_SERVERFAULT`; сначала нужно добавить ответвление через `IBranchs`.
```
IBaseLeader bl = symbols.Leaders.Add(DrawingObjectTypeEnum.ksDrLeader); // ksDrLeader=20
((IBranchs)bl).AddBranchByPoint(0, x, y); // QI; ответвление 0 — остриё (куда указывает стрелка)
bl.SetBranchTextPosition(textX, textY); // положение полки/текста
((ILeader)bl).TextOnShelf.Str = text; // надпись на полке (.Str — наш interop)
// опц.: ((ILeader)bl).ShelfDirection = ksShelfDirectionEnum.ksLSRight;
bl.Update(); // True, bl.Valid == True
```
**Проверено:** `AddBranchByPoint(0,x,y)``BranchCount=1`; `Update=True`, `Valid=True`,
`TextOnShelf.Str` round-trip; `Leaders.Count` +1. Работает с явной `ShelfDirection` и без неё.
`BaseLeader` реализует `IBaseLeader`/`IDrawingObject`; `IBranchs` и `ILeader` — COM-QI от него.
Интерфейсы: `ILeaders.Add(DrawingObjectTypeEnum)``BaseLeader` (для обычной выноски `ksDrLeader=20`);
`IBranchs.AddBranchByPoint(int index, double x, double y)`; `IBaseLeader.SetBranchTextPosition(x,y)` /
`Update` / `Valid` / `Delete` / `ArrowType` (`ksArrowEnum`); `ILeader.TextOnShelf` (`IText`),
`ShelfDirection` (`ksShelfDirectionEnum`: `ksLSNone=0`/`ksLSRight=1`/`ksLSUp=2`/`ksLSDown=3`/`ksLSLeft=-1`).
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_add_leader` | `x,y` (остриё — куда указывает стрелка), `textX,textY` (полка/якорь текста), `text`, `shelfDirection="auto"` (auto\|right\|left\|up\|down), `viewNumber=0` | Поставить выноску на виде: стрелка из полки указывает в (x,y), текст `text` на полке у (textX,textY). Координаты — ЛОКАЛЬНАЯ СК вида (мм). `shelfDirection` — направление полки (auto = по умолчанию КОМПАС). Возвращает текст (read-back) и номер вида. |
## Архитектура
В `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`).
- `AddLeaderAsync(viewNumber, x, y, textX, textY, text, shelfDirection, ct)``DrawingAnnotationResult`
(переиспользуем: `Value`=read-back текст, `ViewNumber`).
- Использует существующий `RequireSymbols2DContainer(viewNumber)` (выноски в `ISymbols2DContainer.Leaders`).
- Новый файл `src/Kompas.Mcp.Core/Drawings/ShelfDirection.cs`: `enum ShelfDirection {Auto,Right,Left,Up,Down}`
(XML-`<summary>` на членах) + `ShelfDirections.Parse(string)` / `ToKompas(ShelfDirection)→ksShelfDirectionEnum`
по образцу `AngleDimensionTypes`. `Auto` → не задавать `ShelfDirection` (оставить дефолт КОМПАС).
- Счётчик `GetViewLeaderCountAsync(viewNumber)` (для теста попадания в вид).
## Реализация
`AddLeaderCore`: `RequireNonEmptyText(text)` + `RequireFiniteCoords(x,y,textX,textY)`
`RequireSymbols2DContainer``symbols.Leaders` (null-check) → `.Add(ksDrLeader)` (null-check) →
**внутри `try/catch`+`Delete`**: `((IBranchs)bl).AddBranchByPoint(0, x, y)` (QI null-check),
`bl.SetBranchTextPosition(textX, textY)`, `((ILeader)bl).TextOnShelf` (null-check) `.Str = text`,
если `shelfDirection != Auto`: `((ILeader)bl).ShelfDirection = ShelfDirections.ToKompas(...)`
`bl.Update()` (FALSE → откат) → `bl.Valid` (false → откат) → read-back `((ILeader)bl).TextOnShelf.Str`
`DrawingAnnotationResult{Value=read-back, ViewNumber}`.
RCW точечно не освобождаем (консистентно; долг v2-2).
## Валидация (чистые static, unit)
- `RequireNonEmptyText(text, nameof(text))`, `RequireFiniteCoords(x,y,textX,textY)`,
`RequireDistinctPoints(x, y, textX, textY)` (остриё ≠ полка) — все уже есть.
- `ShelfDirections.Parse``auto/right/left/up/down` (+ рус. `авто/вправо/влево/вверх/вниз`, регистр/пробелы);
неизвестное → `ArgumentException`; null → `ArgumentNullException`. `ToKompas` (→ `ksShelfDirectionEnum`)
НЕ покрываем unit (граница проекта); бросает `ArgumentOutOfRangeException` на `Auto`.
## Тестирование
### Unit (`ShelfDirectionsTests`)
- `Parse`: `auto/right/left/up/down` (+ регистр, рус.) → enum; неизвестное → `ArgumentException`; null → `ArgumentNullException`.
### Integration (`DrawingLeaderTests`; модель — коробка + стандартные виды)
1. **Ставится + round-trip + viewNumber=0**: `text="Примечание 1"`, `shelfDirection=auto`, `viewNumber=0`
`Value=="Примечание 1"` (read-back), `viewNumber=0` резолвится в первый вид (`r.ViewNumber` совпал),
`Leaders.Count` целевого вида +1.
2. **shelfDirection=left** (не-auto путь) на целевом виде → ставится, `Leaders.Count` +1.
3. **Пустой текст** (`""` и `" "`) → `ArgumentException` (до Add).
4. **Вырожденная** (остриё совпадает с полкой) → `ArgumentException` (до Add).
5. **Нет видов** → понятная ошибка. 6. **Несуществующий `viewNumber>0`** → понятная ошибка.
## Дальнейшее (вне спека)
- Многострочный текст полки / текст под полкой (`TextUnderShelf`); несколько ответвлений
(`AddBranchByPoint` с разными индексами); привязка к геометрии (`IBranchs.SetBaseObject`); тип стрелки
(`ArrowType`); специальные выноски (позиция/клеймо/маркер: `ksDrPosLeader/ksDrBrandLeader/ksDrMarkerLeader`).
Базы (`Bases`), допуски формы (`Tolerances`).
@@ -482,6 +482,66 @@ public sealed class DrawingService
public Task<int> GetViewTextCountAsync(int viewNumber, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => RequireDrawingContainer(viewNumber).Container.DrawingTexts?.Count ?? 0, ct);
/// <summary>
/// Поставить линию-выноску на виде: стрелка из полки указывает в (<paramref name="x"/>,<paramref name="y"/>),
/// текст <paramref name="text"/> на полке у (<paramref name="textX"/>,<paramref name="textY"/>) — всё в
/// ЛОКАЛЬНОЙ СК вида (мм). <paramref name="shelfDirection"/> — направление полки (Auto = по умолчанию КОМПАС).
/// Возвращает текст (read-back) и номер вида.
/// </summary>
public Task<DrawingAnnotationResult> AddLeaderAsync(int viewNumber, double x, double y, double textX,
double textY, string text, ShelfDirection shelfDirection, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => AddLeaderCore(viewNumber, x, y, textX, textY, text, shelfDirection), ct);
private DrawingAnnotationResult AddLeaderCore(int viewNumber, double x, double y, double textX,
double textY, string text, ShelfDirection shelfDirection)
{
DrawingValidation.RequireNonEmptyText(text, nameof(text));
DrawingValidation.RequireFiniteCoords(x, y, textX, textY);
DrawingValidation.RequireDistinctPoints(x, y, textX, textY); // остриё ≠ полка (иначе вырождение)
var (view, symbols) = RequireSymbols2DContainer(viewNumber);
var leaders = symbols.Leaders
?? throw new InvalidOperationException("Коллекция Leaders недоступна на виде.");
var baseLeader = leaders.Add(DrawingObjectTypeEnum.ksDrLeader)
?? throw new InvalidOperationException("Leaders.Add вернул null.");
try
{
// КРИТИЧЕСКИЙ ПОРЯДОК: AddBranchByPoint ДОЛЖЕН предшествовать SetBranchTextPosition/Update —
// выноска без ответвлений валит КОМПАС (RPC_E_SERVERFAULT). Проверено спайком.
var branches = baseLeader as IBranchs
?? throw new InvalidOperationException("Выноска не приводится к IBranchs.");
if (!branches.AddBranchByPoint(0, x, y)) // остриё ответвления 0 (куда указывает стрелка)
throw new InvalidOperationException("AddBranchByPoint вернул FALSE — ответвление выноски не создано.");
if (!baseLeader.SetBranchTextPosition(textX, textY)) // положение полки/текста
throw new InvalidOperationException("SetBranchTextPosition вернул FALSE — полка выноски не задана.");
var leader = baseLeader as ILeader
?? throw new InvalidOperationException("Выноска не приводится к ILeader.");
var shelf = leader.TextOnShelf
?? throw new InvalidOperationException("Текст полки выноски недоступен (TextOnShelf == null).");
shelf.Str = text;
if (shelfDirection != ShelfDirection.Auto)
leader.ShelfDirection = ShelfDirections.ToKompas(shelfDirection);
if (!baseLeader.Update())
throw new InvalidOperationException("ILeader.Update вернул FALSE — выноска не построена.");
if (!baseLeader.Valid)
throw new InvalidOperationException("Выноска построена невалидной.");
var readBack = leader.TextOnShelf?.Str ?? string.Empty; // read-back из COM
return new DrawingAnnotationResult { Value = readBack, ViewNumber = view.Number };
}
catch
{
try { baseLeader.Delete(); } catch { /* лучшее усилие */ }
throw;
}
}
/// <summary>Число выносок на виде (для проверки попадания в вид; считает все подтипы выносок).</summary>
public Task<int> GetViewLeaderCountAsync(int viewNumber, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.Leaders?.Count ?? 0, ct);
/// <summary>
/// Задать технические требования активного чертежа (единый блок над основной надписью; перезаписывает
/// прежние). <paramref name="text"/> — строки через \n. Возвращает число строк.
@@ -0,0 +1,52 @@
using Kompas6Constants;
namespace Kompas.Mcp.Core.Drawings;
/// <summary>Направление полки выноски (обёртка над <see cref="ksShelfDirectionEnum"/>).</summary>
public enum ShelfDirection
{
/// <summary>Авто — направление полки оставляет КОМПАС (свойство не задаётся).</summary>
Auto,
/// <summary>Полка вправо (в ЛОКАЛЬНОЙ СК вида).</summary>
Right,
/// <summary>Полка влево (в ЛОКАЛЬНОЙ СК вида).</summary>
Left,
/// <summary>Полка вверх (в ЛОКАЛЬНОЙ СК вида).</summary>
Up,
/// <summary>Полка вниз (в ЛОКАЛЬНОЙ СК вида).</summary>
Down,
}
/// <summary>Маппинг направления полки ↔ перечисление КОМПАС и разбор строки.</summary>
public static class ShelfDirections
{
/// <summary>В <see cref="ksShelfDirectionEnum"/>. Бросает на <see cref="ShelfDirection.Auto"/> —
/// «авто» обрабатывается в сервисе (свойство не задаётся), сюда передавать нельзя.</summary>
public static ksShelfDirectionEnum ToKompas(ShelfDirection direction) => direction switch
{
ShelfDirection.Right => ksShelfDirectionEnum.ksLSRight,
ShelfDirection.Left => ksShelfDirectionEnum.ksLSLeft,
ShelfDirection.Up => ksShelfDirectionEnum.ksLSUp,
ShelfDirection.Down => ksShelfDirectionEnum.ksLSDown,
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction,
"ShelfDirection.Auto не имеет COM-значения (направление не задаётся)."),
};
/// <summary>Разобрать строку направления (англ./рус., регистр/пробелы) в <see cref="ShelfDirection"/>.</summary>
public static ShelfDirection Parse(string value)
{
ArgumentNullException.ThrowIfNull(value);
return value.Trim().ToLowerInvariant() switch
{
"auto" or "авто" => ShelfDirection.Auto,
"right" or "вправо" => ShelfDirection.Right,
"left" or "влево" => ShelfDirection.Left,
"up" or "вверх" => ShelfDirection.Up,
"down" or "вниз" => ShelfDirection.Down,
_ => throw new ArgumentException(
$"Неизвестное направление полки '{value}'. Допустимо: auto/авто, right/вправо, " +
"left/влево, up/вверх, down/вниз.",
nameof(value)),
};
}
}
+22
View File
@@ -207,4 +207,26 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
var orient = r.Landscape ? "альбомная" : "книжная";
return $"Формат листа {sheetNumber}: {r.Format}, {r.Width:0.#}×{r.Height:0.#} мм, {orient}.";
}
[McpServerTool(Name = "drawing_add_leader")]
[Description("Поставить линию-выноску с текстом на виде активного чертежа. Стрелка выноски указывает " +
"в точку (x,y); текст text размещается на полке у точки (textX,textY) — всё в ЛОКАЛЬНОЙ СК вида (мм). " +
"shelfDirection: auto/авто (по умолчанию — КОМПАС сам) | right/вправо | left/влево | up/вверх | " +
"down/вниз — направление полки. " +
"viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный). Точка острия (x,y) " +
"должна отличаться от точки полки (textX,textY). Возвращает поставленный текст и номер вида.")]
public async Task<string> AddLeader(
[Description("X острия — куда указывает стрелка (СК вида, мм)")] double x,
[Description("Y острия")] double y,
[Description("X полки/якоря текста")] double textX,
[Description("Y полки/якоря текста")] double textY,
[Description("Текст надписи на полке")] string text,
[Description("Направление полки: auto | right | left | up | down")] string shelfDirection = "auto",
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
{
await session.ConnectAsync();
var dir = ShelfDirections.Parse(shelfDirection);
var r = await drawing.AddLeaderAsync(viewNumber, x, y, textX, textY, text, dir);
return $"Выноска поставлена на виде {r.ViewNumber}: «{r.Value}».";
}
}
@@ -0,0 +1,146 @@
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Drawings;
using Kompas.Mcp.Core.Modeling;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: линия-выноска с текстом на виде (API7 ISymbols2DContainer.Leaders + IBranchs).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class DrawingLeaderTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly DrawingService _drawing;
public DrawingLeaderTests(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 Leader_is_placed_round_trip_and_resolves_default_view()
{
var partFile = await BuildAndSaveBoxAsync();
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
var first = views.ViewNumbers[0];
var before = await _drawing.GetViewLeaderCountAsync(first);
// viewNumber=0 → первый несистемный вид (= first)
var r = await _drawing.AddLeaderAsync(0, x: 5, y: 5, textX: 25, textY: 20,
text: "Примечание 1", shelfDirection: ShelfDirection.Auto);
var after = await _drawing.GetViewLeaderCountAsync(first);
Assert.Equal(first, r.ViewNumber);
Assert.Equal("Примечание 1", r.Value); // read-back из COM
Assert.Equal(before + 1, after);
}
finally { await _docs.CloseAsync(save: false); }
}
[Theory]
[InlineData(ShelfDirection.Right)]
[InlineData(ShelfDirection.Left)]
[InlineData(ShelfDirection.Up)]
[InlineData(ShelfDirection.Down)]
public async Task Leader_with_explicit_shelf_direction(ShelfDirection dir)
{
var partFile = await BuildAndSaveBoxAsync();
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.GetViewLeaderCountAsync(target);
// Покрывает всю поверхность ShelfDirections.ToKompas (Right/Left/Up/Down) на реальном COM.
var r = await _drawing.AddLeaderAsync(target, x: 10, y: 8, textX: 30, textY: 25,
text: "Полка", shelfDirection: dir);
var after = await _drawing.GetViewLeaderCountAsync(target);
Assert.Equal(before + 1, after);
Assert.Equal("Полка", r.Value);
}
finally { await _docs.CloseAsync(save: false); }
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task Empty_text_throws(string text)
{
var partFile = await BuildAndSaveBoxAsync();
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
await Assert.ThrowsAsync<ArgumentException>(() =>
_drawing.AddLeaderAsync(0, x: 5, y: 5, textX: 25, textY: 20, text: text,
shelfDirection: ShelfDirection.Auto));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Degenerate_tip_equals_shelf_throws()
{
var partFile = await BuildAndSaveBoxAsync();
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150);
await Assert.ThrowsAsync<ArgumentException>(() =>
_drawing.AddLeaderAsync(0, x: 5, y: 5, textX: 5, textY: 5, text: "X",
shelfDirection: ShelfDirection.Auto));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task No_views_throws()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_drawing.AddLeaderAsync(0, x: 5, y: 5, textX: 25, textY: 20, text: "X",
shelfDirection: ShelfDirection.Auto));
}
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.AddLeaderAsync(999, x: 5, y: 5, textX: 25, textY: 20, text: "X",
shelfDirection: ShelfDirection.Auto));
}
finally { await _docs.CloseAsync(save: false); }
}
}
@@ -0,0 +1,32 @@
using Kompas.Mcp.Core.Drawings;
namespace Kompas.Mcp.Tests;
[Trait("Category", "Unit")]
public sealed class ShelfDirectionsTests
{
[Theory]
[InlineData("auto", ShelfDirection.Auto)]
[InlineData("right", ShelfDirection.Right)]
[InlineData("left", ShelfDirection.Left)]
[InlineData("up", ShelfDirection.Up)]
[InlineData("down", ShelfDirection.Down)]
[InlineData(" RIGHT ", ShelfDirection.Right)]
[InlineData("авто", ShelfDirection.Auto)]
[InlineData("вправо", ShelfDirection.Right)]
[InlineData("влево", ShelfDirection.Left)]
[InlineData("вверх", ShelfDirection.Up)]
[InlineData("вниз", ShelfDirection.Down)]
public void Parse_maps_known(string value, ShelfDirection expected)
=> Assert.Equal(expected, ShelfDirections.Parse(value));
[Fact]
public void Parse_throws_on_unknown()
=> Assert.Throws<ArgumentException>(() => ShelfDirections.Parse("diagonal"));
[Fact]
public void Parse_throws_on_null()
=> Assert.Throws<ArgumentNullException>(() => ShelfDirections.Parse(null!));
// ToKompas (→ ksShelfDirectionEnum) — граница проекта; для Auto бросает (проверяется косвенно).
}