Merge feat/drawing-sheet-format: формат и ориентация листа (инкремент 8)

drawing_set_sheet_format (A0–A5/user + книжная/альбомная) через ILayoutSheet.Format.
82 инструмента, 308 тестов зелёных. Ревью: Codex + pi/glm-5.1 + pi/kimi-k2.6.
This commit is contained in:
2026-05-27 23:34:33 +03:00
15 changed files with 567 additions and 21 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**. **81 MCP tools, 278 tests green (168 unit + 110 integration).**
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).**
Where to look (single source of truth — do **not** duplicate these lists here):
- **Full tool catalog** (by group, all 81) → [`README.md`](README.md) §«Инструменты».
- **Full tool catalog** (by group, all 82) → [`README.md`](README.md) §«Инструменты».
- **Roadmap / what's already done** → [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §10.
- **Backlog / what's left** → [`docs/TODO.md`](docs/TODO.md) (canonical; top of «2D-ЧЕРТЁЖ» = next priority. Associative diametral/radial→circle binding done via `associate` flag — next: arc/angular/rough binding, sheet format, leaders, bases, tolerances; plus extra mate types, package E continuation).
- **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).
- **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_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`).
## КОМПАС-3D API architecture (the critical context)
+3 -3
View File
@@ -41,7 +41,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
}
```
## Инструменты (81 инструмент)
## Инструменты (82 инструмента)
| Группа | Инструменты |
|---|---|
@@ -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 | `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` |
| 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/ 278 тестов: unit + integration (integration требуют КОМПАС)
tests/Kompas.Mcp.Tests/ 308 тестов: 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
+6 -1
View File
@@ -224,4 +224,9 @@ stateful-сессии? Пока полагаемся на последовате
### ✅ Ассоциативная привязка диаметрального/радиального к окружности — реализована (инкремент 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`).
Нереализовано (следующие инкременты): привязка к дуге (`IDrawingContainer.Arcs`), ассоциативный угловой размер (`BaseObject1/2`, отрезки), ассоциативная шероховатость (`IRough.BaseObject`); рамка/основная надпись по ГОСТ-стилю (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов; выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`).
### ✅ Формат и ориентация листа чертежа — реализованы (инкремент 8 вехи «2D-чертёж»)
**Решено.** Новый инструмент `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`), несколько листов.
+3 -2
View File
@@ -22,8 +22,9 @@
-**Угловой** → пара отрезков (`BaseObject1/2`, `LineSegments`).
-**Шероховатость**`IRough.BaseObject` (привязка к контуру/ребру).
- ⛔/☐ **Линейный**у `ILineDimension` нет `BaseObject`; нужен иной механизм (отложено).
- **Рамка/формат листа** (A4/A3…, ориентация) — `ILayoutSheet`/`SheetFormat`. Быстрый фундаментальный
шаг, вероятно без спайка. Делает чертёж «правильным» по ГОСТ.
- **Формат/ориентация листа** (A0A5/user, книжная/альбомная) — `drawing_set_sheet_format`
(инкремент 8); `ILayoutSheet.Format`/`ISheetFormat`. Остаётся: ☐ рамка/основная надпись по
конкретному ГОСТ-стилю оформления (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов.
-**Выноски** (`Leaders`) — `ISymbols2DContainer.Leaders` (полки-выноски с текстом).
-**Обозначения баз** (`Bases`) — `ISymbols2DContainer.Bases`.
-**Допуски формы и расположения** (`Tolerances`) — `ISymbols2DContainer.Tolerances` (обычно в связке с базой).
+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 (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+drawing_set_sheet_format (82 инструмента)</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 (ассоциативная привязка диам./радиального) · навык kompas-3d · 81 инструмент · 278 тестов</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 (формат листа) · навык kompas-3d · 82 инструмента · 308 тестов</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">81 инструмент, отдаются по MCP-протоколу</p>
<p class="meta">82 инструмента, отдаются по 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)</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>
<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>
</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">Итого 278 тестов зелёных (168 unit + 110 integration)</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">Сборка <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>
@@ -357,7 +357,8 @@
<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" 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 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>
</div>
</section>
@@ -0,0 +1,133 @@
# Дизайн: формат и ориентация листа чертежа (drawing_set_sheet_format) через API7
**Дата:** 2026-05-27
**Статус:** дизайн согласован, спайк проведён, ревью pi/glm-5.1 + pi/kimi-k2.6 учтено — к реализации
## Правки по ревью реализации (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- **K1 (kimi, Major)** — опасение, что для `User` read-back ориентации устаревший (VerticalOrientation
не задаётся). **Снято спайком:** КОМПАС САМ выводит `VerticalOrientation` из соотношения W/H
(500×300→альбомная, 300×500→книжная), игнорирует заданный флаг и НЕ свопает W/H → read-back верный,
код корректен. Документировано комментарием; добавлены тесты на `Landscape` для User (обе ориентации).
- **K2** — `ValidateFormatDimensions` теперь называет точный нарушивший параметр (`width`/`height`).
- **C1/G1/K3** — тесты User проверяют `Landscape`. **C2/G3** — тест `sheetNumber=0``ArgumentOutOfRangeException`.
**C3** — A1 в `Parse`-тесте. **G2** — тест A4 landscape (своп 297×210).
- Отклонено (нит): **G4** алиасы в описании (lenient parse — намеренно), **G5** сообщение при NaN
(для стандартного «размеры только для user» приемлемо), **K5** тип исключения для стандартного формата (ArgumentException
для «неверная комбинация» vs AOORE для «вне диапазона» — намеренное различие, на нём держатся тесты),
**G6** «нет unit для ValidateFormatDimensions» — ложно, покрыто в `DrawingValidationTests`.
## Правки по ревью спека (pi/glm-5.1 + pi/kimi-k2.6)
- **A** для `user` флаг `landscape` **игнорируется** — ориентация задаётся соотношением `width`/`height`
(КОМПАС сам выставит `VerticalOrientation`). `VerticalOrientation=!landscape` ставим ТОЛЬКО для
стандартных форматов (избегаем неоднозначного свопа W/H). Задокументировано в описании инструмента.
- **B/E** `width`/`height` для стандартного формата запрещены: валидатор `ValidateFormatDimensions`
(для `User` — оба конечны и `>0`; для стандартного — оба `==0`, иначе `ArgumentException` «размеры
только для user»).
- **C** read-back формата возвращает дружелюбное имя через новый `PaperFormats.FromKompas(ksEnum)`
`SheetFormatResult.Format` = `"A3"`/`"User"` (симметрия вход/выход), а не COM-имя `"ksFormatA3"`.
- **D** `RequireLayoutSheet(sheetNumber)`: `sheetNumber>0` (`ArgumentOutOfRangeException`) + null-check
после `ItemByNumber[sheetNumber]` (`InvalidOperationException` «нет листа N»); + интеграционный тест.
- **F** read-back `fmt2 = sheet.Format` с null-check.
- **G** `SheetFormatResult``public sealed record` с `public required … { get; init; }` (как
`DrawingDimensionResult`/`DrawingViewsResult`).
- **H** интеграционные сравнения размеров — через `Assert.InRange` (допуск на float).
- **I** члены `PaperFormat`с XML-`<summary>` (как `RoughSignType`).
- **J** `FormatMultiplicity` оставляем на COM-дефолте (1) — отмечено в реализации.
- **K** (верхняя граница `width`/`height`) — отклонено, в OPEN_QUESTIONS как будущее упрочнение
(консистентно с `RequirePositiveRadius` — верхней границы нет).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 8. Задать **формат** (A0–A5 или пользовательский) и **ориентацию**
(книжная/альбомная) листа активного чертежа. Фундамент «правильного» по ГОСТ чертежа (сейчас лист
всегда дефолтный A4 книжный).
## Спайк: механизм подтверждён вживую (НЕ разучивать)
Спайк (`_SpikeSheetFormat`, прогнан на реальном КОМПАС, затем удалён): новый чертёж →
`doc.LayoutSheets.ItemByNumber[1]` (`ILayoutSheet`) → `sheet.Format` (`ISheetFormat`).
```
ISheetFormat fmt = sheet.Format;
fmt.Format = ksDocumentFormatEnum.ksFormatA3; // A0=0..A5=5, User=6
fmt.VerticalOrientation = false; // true=книжная (portrait), false=альбомная (landscape)
sheet.Update(); // True
// для стандартного формата FormatWidth/Height пересчитываются АВТО (A3 landscape → 420×297)
```
**Проверено:**
- Дефолт нового чертежа: `ksFormatA4`, `VerticalOrientation=true`, 210×297, `FormatMultiplicity=1`.
- A3 landscape: `Update=True`, read-back `Format=ksFormatA3`, `Vertical=False`, **W=420, H=297 (авто)**
для стандартных форматов размеры не задаём, их даёт enum+ориентация.
- A4 portrait: 210×297.
- Пользовательский: `Format=ksFormatUser` + `FormatWidth=500` + `FormatHeight=300` + `Update` → применён.
`ksDocumentFormatEnum`: `ksFormatA0=0`, `A1=1`, `A2=2`, `A3=3`, `A4=4`, `A5=5`, `ksFormatUser=6`.
`ISheetFormat`: `Format` (enum), `VerticalOrientation` (bool), `FormatWidth`/`FormatHeight` (double, мм),
`FormatMultiplicity` (int).
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_set_sheet_format` | `format="A4"` (A0\|A1\|A2\|A3\|A4\|A5\|user), `landscape=false` (false=книжная), `width=0`,`height=0` (ТОЛЬКО для `user`, мм, >0), `sheetNumber=1` | Задать формат и ориентацию листа `sheetNumber` активного чертежа. Стандартный формат — размеры авто (по enum+ориентации), `width`/`height` для него запрещены (должны быть 0). `user` — задать `width`/`height` (>0); для `user` флаг `landscape` ИГНОРИРУЕТСЯ (ориентация — из соотношения сторон). Возвращает итоговый формат («A3»/«User»), ширину, высоту, ориентацию (read-back). |
## Архитектура
В `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`).
- `SetSheetFormatAsync(PaperFormat format, bool landscape, double width, double height, int sheetNumber, ct)`
`SheetFormatResult`.
- Helper `RequireLayoutSheet(sheetNumber)``ILayoutSheet`: `sheetNumber>0`
(`ArgumentOutOfRangeException`) → нет активного чертежа / `LayoutSheets` null / `ItemByNumber[sheetNumber]`
null («нет листа N») — раздельная диагностика, рядом с `RequireActiveStamp`.
- Новый файл `src/Kompas.Mcp.Core/Drawings/PaperFormat.cs`: `enum PaperFormat {A0,A1,A2,A3,A4,A5,User}`
(каждый член с XML-`<summary>`) + `PaperFormats.Parse(string)` / `ToKompas(PaperFormat)→ksDocumentFormatEnum`
/ `FromKompas(ksDocumentFormatEnum)→PaperFormat` (для read-back дружелюбного имени), по образцу `RoughSignTypes`.
- Новый файл `src/Kompas.Mcp.Core/Drawings/SheetFormatResult.cs`:
`public sealed record SheetFormatResult { public required string Format; public required double Width;
public required double Height; public required bool Landscape; }` (стиль `DrawingDimensionResult`).
## Реализация
`SetSheetFormatCore`: `DrawingValidation.ValidateFormatDimensions(format, width, height)`
`PaperFormats.ToKompas(format)``RequireLayoutSheet(sheetNumber)``fmt = sheet.Format` (null-check) →
`fmt.Format = ksEnum`; if `format==User`: `fmt.FormatWidth=width`, `fmt.FormatHeight=height` (ориентацию
НЕ трогаем — задаётся W/H); else: `fmt.VerticalOrientation = !landscape` (размеры авто) → `sheet.Update()`
(FALSE → ошибка; объект уровня листа, перезапись идемпотентна — отката `Delete` нет) → read-back
`fmt2 = sheet.Format` (null-check) → `SheetFormatResult { Format = PaperFormats.FromKompas(fmt2.Format)
.ToString(), Width = fmt2.FormatWidth, Height = fmt2.FormatHeight, Landscape = !fmt2.VerticalOrientation }`.
`FormatMultiplicity` оставляем на COM-дефолте (1) — не читаем и не задаём. RCW точечно не освобождаем
(консистентно; долг v2-2).
## Валидация (чистые static, unit)
- `PaperFormats.Parse``A0..A5`/`user` (+ регистр/пробелы); неизвестное → `ArgumentException`; null → `ArgumentNullException`.
- Новый `DrawingValidation.ValidateFormatDimensions(PaperFormat format, double width, double height)`:
для `User``width` и `height` оба конечны и `> 0` (иначе `ArgumentOutOfRangeException`); для стандартного —
`width==0 && height==0` (иначе `ArgumentException` «размеры задаются только для format=user»).
- `PaperFormats.ToKompas`/`FromKompas` (↔ `ksDocumentFormatEnum`) НЕ покрываем unit — граница проекта
(тест не ссылается на `Kompas6Constants`), как у прочих enum-маппингов; покрываются интеграцией.
## Тестирование
### Unit (`PaperFormatsTests`, `DrawingValidationTests`)
- `PaperFormats.Parse`: `A4/a3/ A2 /a0/a5/user` → enum; неизвестное → `ArgumentException`; null → `ArgumentNullException`.
- `ValidateFormatDimensions`:
- `User`: бросает на (0,300)/(500,0)/(1,300)/(NaN,300)/(500,∞); пропускает (500,300).
- стандартный (`A4`): бросает на (500,0)/(0,300)/(500,300); пропускает (0,0).
### Integration (`DrawingSheetFormatTests`; сравнения размеров — `Assert.InRange`)
1. **A3 landscape**: `format=A3, landscape=true``Format=="A3"`, `Width≈420`, `Height≈297`, `Landscape==true`.
2. **A4 portrait** (`landscape=false`): → `Format=="A4"`, `Width≈210`, `Height≈297`, `Landscape==false`.
3. **User 500×300**: `format=user, width=500, height=300``Format=="User"`, `Width≈500`, `Height≈300`.
4. **User без размеров** (`width=0`) → `ArgumentOutOfRangeException` (до COM).
5. **Размеры для стандартного** (`format=A3, width=400`) → `ArgumentException` (до COM).
6. **Несуществующий лист** (`sheetNumber=99`) → `InvalidOperationException`.
7. **Активный документ не чертёж** (деталь) → понятная ошибка.
## Дальнейшее (вне спека)
- Рамка/основная надпись по конкретному ГОСТ-стилю оформления (`LayoutLibraryFileName`/`LayoutStyleNumber`),
несколько листов (`LayoutSheets.Add`); привязка к дуге, угловой/шероховатость; выноски/базы/допуски формы.
@@ -84,6 +84,65 @@ public sealed class DrawingService
?? throw new InvalidOperationException("У листа оформления нет основной надписи (Stamp).");
}
/// <summary>Лист оформления чертежа по номеру (1-based) с диагностикой (номер &gt; 0, лист существует).</summary>
private ILayoutSheet RequireLayoutSheet(int sheetNumber)
{
if (sheetNumber <= 0)
throw new ArgumentOutOfRangeException(nameof(sheetNumber), "Номер листа должен быть > 0.");
var doc2d = RequireActiveDrawing();
var sheets = doc2d.LayoutSheets
?? throw new InvalidOperationException("Не удалось получить листы оформления чертежа.");
return sheets.ItemByNumber[sheetNumber] as ILayoutSheet
?? throw new InvalidOperationException($"На чертеже нет листа оформления #{sheetNumber}.");
}
/// <summary>
/// Задать формат (<paramref name="format"/>: A0A5 или user) и ориентацию листа
/// <paramref name="sheetNumber"/> активного чертежа. Для стандартного формата размеры авто
/// (<paramref name="width"/>/<paramref name="height"/> запрещены); для user — заданные width/height (мм),
/// при этом <paramref name="landscape"/> игнорируется (ориентация из соотношения сторон). Возвращает
/// итоговый формат, размеры и ориентацию (read-back из COM).
/// </summary>
public Task<SheetFormatResult> SetSheetFormatAsync(PaperFormat format, bool landscape, double width,
double height, int sheetNumber = 1, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() => SetSheetFormatCore(format, landscape, width, height, sheetNumber), ct);
private SheetFormatResult SetSheetFormatCore(PaperFormat format, bool landscape, double width,
double height, int sheetNumber)
{
DrawingValidation.ValidateFormatDimensions(format, width, height);
var sheet = RequireLayoutSheet(sheetNumber);
var fmt = sheet.Format
?? throw new InvalidOperationException("У листа недоступен формат (Format == null).");
fmt.Format = PaperFormats.ToKompas(format);
if (format == PaperFormat.User)
{
// Для пользовательского КОМПАС САМ выводит VerticalOrientation из соотношения width/height
// (W>H → альбомная, H>W → книжная) и игнорирует заданный флаг, W/H не свопает — проверено
// спайком. Поэтому VerticalOrientation не трогаем; read-back ниже даёт верную ориентацию.
fmt.FormatWidth = width;
fmt.FormatHeight = height;
}
else
{
fmt.VerticalOrientation = !landscape; // стандартный: размеры авто по формату+ориентации
}
// FormatMultiplicity оставляем на COM-дефолте (1).
if (!sheet.Update())
throw new InvalidOperationException("ILayoutSheet.Update вернул FALSE — формат листа не задан.");
var fmt2 = sheet.Format
?? throw new InvalidOperationException("Формат листа недоступен после Update (Format == null).");
return new SheetFormatResult
{
Format = PaperFormats.FromKompas(fmt2.Format).ToString(),
Width = fmt2.FormatWidth,
Height = fmt2.FormatHeight,
Landscape = !fmt2.VerticalOrientation,
};
}
/// <summary>
/// Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ
/// СК вида (мм); размерная линия проходит через (x3,y3). <paramref name="viewNumber"/> — номер вида
@@ -55,6 +55,31 @@ public static class DrawingValidation
throw new ArgumentException("Текст не должен быть пустым.", paramName);
}
/// <summary>
/// Проверить размеры формата листа: для <see cref="PaperFormat.User"/> — ширина и высота оба
/// конечны и &gt; 0; для стандартного формата — оба должны быть 0 (размеры авто, задавать нельзя).
/// </summary>
public static void ValidateFormatDimensions(PaperFormat format, double width, double height)
{
if (format == PaperFormat.User)
{
if (!double.IsFinite(width) || width <= 0)
throw new ArgumentOutOfRangeException(nameof(width),
"Для format=user ширина должна быть конечной и > 0.");
if (!double.IsFinite(height) || height <= 0)
throw new ArgumentOutOfRangeException(nameof(height),
"Для format=user высота должна быть конечной и > 0.");
}
else if (width != 0 || height != 0)
{
// Точное имя нарушившего параметра (а не всегда width).
var offender = width != 0 ? nameof(width) : nameof(height);
throw new ArgumentException(
"Ширина/высота задаются только для format=user; для стандартного формата размеры авто (передайте 0).",
offender);
}
}
/// <summary>Минимальное расстояние между выносными точками линейного размера (мм).</summary>
private const double MinPointDistance = 1e-7;
@@ -0,0 +1,70 @@
using Kompas6Constants;
namespace Kompas.Mcp.Core.Drawings;
/// <summary>Формат листа чертежа (обёртка над <see cref="ksDocumentFormatEnum"/>).</summary>
public enum PaperFormat
{
/// <summary>A0 (841×1189 мм).</summary>
A0,
/// <summary>A1 (594×841 мм).</summary>
A1,
/// <summary>A2 (420×594 мм).</summary>
A2,
/// <summary>A3 (297×420 мм).</summary>
A3,
/// <summary>A4 (210×297 мм).</summary>
A4,
/// <summary>A5 (148×210 мм).</summary>
A5,
/// <summary>Пользовательский формат с явной шириной/высотой.</summary>
User,
}
/// <summary>Маппинг формата листа ↔ перечисление КОМПАС и разбор строки.</summary>
public static class PaperFormats
{
public static ksDocumentFormatEnum ToKompas(PaperFormat format) => format switch
{
PaperFormat.A0 => ksDocumentFormatEnum.ksFormatA0,
PaperFormat.A1 => ksDocumentFormatEnum.ksFormatA1,
PaperFormat.A2 => ksDocumentFormatEnum.ksFormatA2,
PaperFormat.A3 => ksDocumentFormatEnum.ksFormatA3,
PaperFormat.A4 => ksDocumentFormatEnum.ksFormatA4,
PaperFormat.A5 => ksDocumentFormatEnum.ksFormatA5,
PaperFormat.User => ksDocumentFormatEnum.ksFormatUser,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Неизвестный формат листа."),
};
/// <summary>Обратный маппинг (для read-back дружелюбного имени).</summary>
public static PaperFormat FromKompas(ksDocumentFormatEnum format) => format switch
{
ksDocumentFormatEnum.ksFormatA0 => PaperFormat.A0,
ksDocumentFormatEnum.ksFormatA1 => PaperFormat.A1,
ksDocumentFormatEnum.ksFormatA2 => PaperFormat.A2,
ksDocumentFormatEnum.ksFormatA3 => PaperFormat.A3,
ksDocumentFormatEnum.ksFormatA4 => PaperFormat.A4,
ksDocumentFormatEnum.ksFormatA5 => PaperFormat.A5,
ksDocumentFormatEnum.ksFormatUser => PaperFormat.User,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, "Неизвестный формат листа КОМПАС."),
};
/// <summary>Разобрать строку формата (A0..A5 / user, регистр/пробелы) в <see cref="PaperFormat"/>.</summary>
public static PaperFormat Parse(string value)
{
ArgumentNullException.ThrowIfNull(value);
return value.Trim().ToLowerInvariant() switch
{
"a0" => PaperFormat.A0,
"a1" => PaperFormat.A1,
"a2" => PaperFormat.A2,
"a3" => PaperFormat.A3,
"a4" => PaperFormat.A4,
"a5" => PaperFormat.A5,
"user" or "custom" or "пользовательский" => PaperFormat.User,
_ => throw new ArgumentException(
$"Неизвестный формат листа '{value}'. Допустимо: A0, A1, A2, A3, A4, A5, user.",
nameof(value)),
};
}
}
@@ -0,0 +1,17 @@
namespace Kompas.Mcp.Core.Drawings;
/// <summary>Результат задания формата листа чертежа (read-back из COM).</summary>
public sealed record SheetFormatResult
{
/// <summary>Итоговый формат («A0»…«A5» или «User»).</summary>
public required string Format { get; init; }
/// <summary>Ширина листа, мм.</summary>
public required double Width { get; init; }
/// <summary>Высота листа, мм.</summary>
public required double Height { get; init; }
/// <summary>Альбомная ориентация (true) либо книжная (false).</summary>
public required bool Landscape { get; init; }
}
+20
View File
@@ -187,4 +187,24 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
var lines = await drawing.SetTechnicalRequirementsAsync(text);
return $"Технические требования заданы ({lines} стр.).";
}
[McpServerTool(Name = "drawing_set_sheet_format")]
[Description("Задать формат и ориентацию листа активного чертежа. format: A0|A1|A2|A3|A4|A5|user. " +
"landscape: false = книжная (portrait), true = альбомная (landscape). Для стандартного формата " +
"размеры вычисляются автоматически — width/height передавать НЕ нужно (должны быть 0). Для format=user " +
"задайте width и height в мм (>0); при user флаг landscape игнорируется (ориентация — из соотношения " +
"сторон). sheetNumber — номер листа (1 = первый). Возвращает итоговый формат, размеры и ориентацию.")]
public async Task<string> SetSheetFormat(
[Description("Формат: A0|A1|A2|A3|A4|A5|user")] string format = "A4",
[Description("Альбомная ориентация (true) или книжная (false)")] bool landscape = false,
[Description("Ширина листа, мм (только для format=user)")] double width = 0,
[Description("Высота листа, мм (только для format=user)")] double height = 0,
[Description("Номер листа (1 = первый)")] int sheetNumber = 1)
{
await session.ConnectAsync();
var f = PaperFormats.Parse(format);
var r = await drawing.SetSheetFormatAsync(f, landscape, width, height, sheetNumber);
var orient = r.Landscape ? "альбомная" : "книжная";
return $"Формат листа {sheetNumber}: {r.Format}, {r.Width:0.#}×{r.Height:0.#} мм, {orient}.";
}
}
@@ -91,4 +91,32 @@ public sealed class DrawingValidationTests
[Fact]
public void RequireNonEmptyText_accepts_non_empty()
=> Assert.Null(Record.Exception(() => DrawingValidation.RequireNonEmptyText("Ra 1.6", "text")));
[Theory]
[InlineData(0, 300)]
[InlineData(500, 0)]
[InlineData(-1, 300)]
[InlineData(double.NaN, 300)]
[InlineData(500, double.PositiveInfinity)]
public void ValidateFormatDimensions_user_throws_on_non_positive_or_non_finite(double w, double h)
=> Assert.Throws<ArgumentOutOfRangeException>(
() => DrawingValidation.ValidateFormatDimensions(PaperFormat.User, w, h));
[Fact]
public void ValidateFormatDimensions_user_accepts_positive()
=> Assert.Null(Record.Exception(
() => DrawingValidation.ValidateFormatDimensions(PaperFormat.User, 500, 300)));
[Theory]
[InlineData(500, 0)]
[InlineData(0, 300)]
[InlineData(500, 300)]
public void ValidateFormatDimensions_standard_throws_when_dimensions_given(double w, double h)
=> Assert.Throws<ArgumentException>(
() => DrawingValidation.ValidateFormatDimensions(PaperFormat.A3, w, h));
[Fact]
public void ValidateFormatDimensions_standard_accepts_zero_dimensions()
=> Assert.Null(Record.Exception(
() => DrawingValidation.ValidateFormatDimensions(PaperFormat.A3, 0, 0)));
}
@@ -0,0 +1,156 @@
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Drawings;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: формат и ориентация листа чертежа (API7 ILayoutSheet.Format / ISheetFormat).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class DrawingSheetFormatTests : IntegrationTestBase
{
private readonly DocumentService _docs;
private readonly DrawingService _drawing;
public DrawingSheetFormatTests(KompasFixture fx) : base(fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_drawing = new DrawingService(fx.Session, fx.Dispatcher);
}
[Fact]
public async Task A3_landscape()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
var r = await _drawing.SetSheetFormatAsync(PaperFormat.A3, landscape: true, width: 0, height: 0);
Assert.Equal("A3", r.Format);
Assert.InRange(r.Width, 419.5, 420.5);
Assert.InRange(r.Height, 296.5, 297.5);
Assert.True(r.Landscape);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task A4_portrait()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
var r = await _drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: false, width: 0, height: 0);
Assert.Equal("A4", r.Format);
Assert.InRange(r.Width, 209.5, 210.5);
Assert.InRange(r.Height, 296.5, 297.5);
Assert.False(r.Landscape);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task A4_landscape_swaps_dimensions()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
var r = await _drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: true, width: 0, height: 0);
Assert.Equal("A4", r.Format);
Assert.InRange(r.Width, 296.5, 297.5); // своп: альбомная A4 = 297×210
Assert.InRange(r.Height, 209.5, 210.5);
Assert.True(r.Landscape);
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task User_landscape_dimensions_derive_orientation()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
// 500×300 (W>H) → КОМПАС выводит альбомную ориентацию (Landscape=true), W/H литеральны.
var r = await _drawing.SetSheetFormatAsync(PaperFormat.User, landscape: false, width: 500, height: 300);
Assert.Equal("User", r.Format);
Assert.InRange(r.Width, 499.5, 500.5);
Assert.InRange(r.Height, 299.5, 300.5);
Assert.True(r.Landscape); // ориентация выведена из соотношения сторон (не из флага landscape=false)
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task User_portrait_dimensions_derive_orientation()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
// 300×500 (H>W) → книжная (Landscape=false).
var r = await _drawing.SetSheetFormatAsync(PaperFormat.User, landscape: true, width: 300, height: 500);
Assert.Equal("User", r.Format);
Assert.InRange(r.Width, 299.5, 300.5);
Assert.InRange(r.Height, 499.5, 500.5);
Assert.False(r.Landscape); // landscape=true проигнорирован — ориентация из сторон
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task User_without_dimensions_throws()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
_drawing.SetSheetFormatAsync(PaperFormat.User, landscape: false, width: 0, height: 0));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Standard_with_dimensions_throws()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await Assert.ThrowsAsync<ArgumentException>(() =>
_drawing.SetSheetFormatAsync(PaperFormat.A3, landscape: false, width: 400, height: 0));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Nonexistent_sheet_throws()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: false, width: 0, height: 0, sheetNumber: 99));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Nonpositive_sheet_number_throws()
{
await _docs.CreateAsync(KompasDocumentType.Drawing);
try
{
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
_drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: false, width: 0, height: 0, sheetNumber: 0));
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Active_document_not_drawing_throws()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: false, width: 0, height: 0));
}
finally { await _docs.CloseAsync(save: false); }
}
}
@@ -0,0 +1,29 @@
using Kompas.Mcp.Core.Drawings;
namespace Kompas.Mcp.Tests;
[Trait("Category", "Unit")]
public sealed class PaperFormatsTests
{
[Theory]
[InlineData("A4", PaperFormat.A4)]
[InlineData("a3", PaperFormat.A3)]
[InlineData(" A2 ", PaperFormat.A2)]
[InlineData("a0", PaperFormat.A0)]
[InlineData("A1", PaperFormat.A1)]
[InlineData("A5", PaperFormat.A5)]
[InlineData("user", PaperFormat.User)]
[InlineData("USER", PaperFormat.User)]
public void Parse_maps_known(string value, PaperFormat expected)
=> Assert.Equal(expected, PaperFormats.Parse(value));
[Fact]
public void Parse_throws_on_unknown()
=> Assert.Throws<ArgumentException>(() => PaperFormats.Parse("A6"));
[Fact]
public void Parse_throws_on_null()
=> Assert.Throws<ArgumentNullException>(() => PaperFormats.Parse(null!));
// ToKompas/FromKompas (↔ ksDocumentFormatEnum) — граница проекта, проверяются интеграцией.
}