diff --git a/docs/superpowers/specs/2026-05-27-drawing-sheet-format-design.md b/docs/superpowers/specs/2026-05-27-drawing-sheet-format-design.md new file mode 100644 index 0000000..98fad39 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-drawing-sheet-format-design.md @@ -0,0 +1,119 @@ +# Дизайн: формат и ориентация листа чертежа (drawing_set_sheet_format) через API7 + +**Дата:** 2026-05-27 +**Статус:** дизайн согласован, спайк проведён, ревью pi/glm-5.1 + pi/kimi-k2.6 учтено — к реализации + +## Правки по ревью (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-`` (как `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-``) + `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`); привязка к дуге, угловой/шероховатость; выноски/базы/допуски формы. diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs index 10e8aaa..faa94ec 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -84,6 +84,63 @@ public sealed class DrawingService ?? throw new InvalidOperationException("У листа оформления нет основной надписи (Stamp)."); } + /// Лист оформления чертежа по номеру (1-based) с диагностикой (номер > 0, лист существует). + 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}."); + } + + /// + /// Задать формат (: A0–A5 или user) и ориентацию листа + /// активного чертежа. Для стандартного формата размеры авто + /// (/ запрещены); для user — заданные width/height (мм), + /// при этом игнорируется (ориентация из соотношения сторон). Возвращает + /// итоговый формат, размеры и ориентацию (read-back из COM). + /// + public Task 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) + { + // Для пользовательского ориентация задаётся соотношением width/height — VerticalOrientation не трогаем. + 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, + }; + } + /// /// Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ /// СК вида (мм); размерная линия проходит через (x3,y3). — номер вида diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs b/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs index 2cbefa9..ab4108c 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs @@ -55,6 +55,26 @@ public static class DrawingValidation throw new ArgumentException("Текст не должен быть пустым.", paramName); } + /// + /// Проверить размеры формата листа: для — ширина и высота оба + /// конечны и > 0; для стандартного формата — оба должны быть 0 (размеры авто, задавать нельзя). + /// + public static void ValidateFormatDimensions(PaperFormat format, double width, double height) + { + if (format == PaperFormat.User) + { + if (!double.IsFinite(width) || width <= 0 || !double.IsFinite(height) || height <= 0) + throw new ArgumentOutOfRangeException(nameof(width), + "Для format=user ширина и высота должны быть конечными и > 0."); + } + else if (width != 0 || height != 0) + { + throw new ArgumentException( + "Ширина/высота задаются только для format=user; для стандартного формата размеры авто (передайте 0).", + nameof(width)); + } + } + /// Минимальное расстояние между выносными точками линейного размера (мм). private const double MinPointDistance = 1e-7; diff --git a/src/Kompas.Mcp.Core/Drawings/PaperFormat.cs b/src/Kompas.Mcp.Core/Drawings/PaperFormat.cs new file mode 100644 index 0000000..ea5cdd7 --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/PaperFormat.cs @@ -0,0 +1,70 @@ +using Kompas6Constants; + +namespace Kompas.Mcp.Core.Drawings; + +/// Формат листа чертежа (обёртка над ). +public enum PaperFormat +{ + /// A0 (841×1189 мм). + A0, + /// A1 (594×841 мм). + A1, + /// A2 (420×594 мм). + A2, + /// A3 (297×420 мм). + A3, + /// A4 (210×297 мм). + A4, + /// A5 (148×210 мм). + A5, + /// Пользовательский формат с явной шириной/высотой. + User, +} + +/// Маппинг формата листа ↔ перечисление КОМПАС и разбор строки. +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, "Неизвестный формат листа."), + }; + + /// Обратный маппинг (для read-back дружелюбного имени). + 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, "Неизвестный формат листа КОМПАС."), + }; + + /// Разобрать строку формата (A0..A5 / user, регистр/пробелы) в . + 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)), + }; + } +} diff --git a/src/Kompas.Mcp.Core/Drawings/SheetFormatResult.cs b/src/Kompas.Mcp.Core/Drawings/SheetFormatResult.cs new file mode 100644 index 0000000..befe2f3 --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/SheetFormatResult.cs @@ -0,0 +1,17 @@ +namespace Kompas.Mcp.Core.Drawings; + +/// Результат задания формата листа чертежа (read-back из COM). +public sealed record SheetFormatResult +{ + /// Итоговый формат («A0»…«A5» или «User»). + public required string Format { get; init; } + + /// Ширина листа, мм. + public required double Width { get; init; } + + /// Высота листа, мм. + public required double Height { get; init; } + + /// Альбомная ориентация (true) либо книжная (false). + public required bool Landscape { get; init; } +} diff --git a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs index 966a8d0..c4e5907 100644 --- a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs +++ b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs @@ -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 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}."; + } } diff --git a/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs b/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs index b072e47..87c82d5 100644 --- a/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs +++ b/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs @@ -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( + () => 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( + () => DrawingValidation.ValidateFormatDimensions(PaperFormat.A3, w, h)); + + [Fact] + public void ValidateFormatDimensions_standard_accepts_zero_dimensions() + => Assert.Null(Record.Exception( + () => DrawingValidation.ValidateFormatDimensions(PaperFormat.A3, 0, 0))); } diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingSheetFormatTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingSheetFormatTests.cs new file mode 100644 index 0000000..f5a3b66 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingSheetFormatTests.cs @@ -0,0 +1,111 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: формат и ориентация листа чертежа (API7 ILayoutSheet.Format / ISheetFormat). +[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 User_format_with_dimensions() + { + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + 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); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task User_without_dimensions_throws() + { + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await Assert.ThrowsAsync(() => + _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(() => + _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(() => + _drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: false, width: 0, height: 0, sheetNumber: 99)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Active_document_not_drawing_throws() + { + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + await Assert.ThrowsAsync(() => + _drawing.SetSheetFormatAsync(PaperFormat.A4, landscape: false, width: 0, height: 0)); + } + finally { await _docs.CloseAsync(save: false); } + } +} diff --git a/tests/Kompas.Mcp.Tests/PaperFormatsTests.cs b/tests/Kompas.Mcp.Tests/PaperFormatsTests.cs new file mode 100644 index 0000000..db232fc --- /dev/null +++ b/tests/Kompas.Mcp.Tests/PaperFormatsTests.cs @@ -0,0 +1,28 @@ +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("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(() => PaperFormats.Parse("A6")); + + [Fact] + public void Parse_throws_on_null() + => Assert.Throws(() => PaperFormats.Parse(null!)); + + // ToKompas/FromKompas (↔ ksDocumentFormatEnum) — граница проекта, проверяются интеграцией. +}