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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
@@ -22,8 +22,9 @@
|
||||
- ☐ **Угловой** → пара отрезков (`BaseObject1/2`, `LineSegments`).
|
||||
- ☐ **Шероховатость** → `IRough.BaseObject` (привязка к контуру/ребру).
|
||||
- ⛔/☐ **Линейный** — у `ILineDimension` нет `BaseObject`; нужен иной механизм (отложено).
|
||||
- ☐ **Рамка/формат листа** (A4/A3…, ориентация) — `ILayoutSheet`/`SheetFormat`. Быстрый фундаментальный
|
||||
шаг, вероятно без спайка. Делает чертёж «правильным» по ГОСТ.
|
||||
- ✓ **Формат/ориентация листа** (A0–A5/user, книжная/альбомная) — `drawing_set_sheet_format`
|
||||
(инкремент 8); `ILayoutSheet.Format`/`ISheetFormat`. Остаётся: ☐ рамка/основная надпись по
|
||||
конкретному ГОСТ-стилю оформления (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов.
|
||||
- ☐ **Выноски** (`Leaders`) — `ISymbols2DContainer.Leaders` (полки-выноски с текстом).
|
||||
- ☐ **Обозначения баз** (`Bases`) — `ISymbols2DContainer.Bases`.
|
||||
- ☐ **Допуски формы и расположения** (`Tolerances`) — `ISymbols2DContainer.Tolerances` (обычно в связке с базой).
|
||||
|
||||
@@ -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 A0–A5/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, A0–A5/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) с диагностикой (номер > 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"/>: A0–A5 или 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"/> — ширина и высота оба
|
||||
/// конечны и > 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; }
|
||||
}
|
||||
@@ -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) — граница проекта, проверяются интеграцией.
|
||||
}
|
||||
Reference in New Issue
Block a user