/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.App/Program.cs
157 строк
7 KB
docNemo
Фундамент продукта: параметрическое построение выкроек, печать, установщик
08 авг 2026, 22:17
08 авг 2026, 22:17
cd47cce
Код
Авторство
О чём код?
using System; using System.IO; using System.Linq; using Avalonia; using ClothesGraph.Model.Grading; using ClothesGraph.Model.Seams; using ClothesGraph.Model.Storage; using ClothesGraph.Printing; namespace ClothesGraph.App; internal static class Program { /// <summary> /// Путь для снимка окна. Задаётся ключом --screenshot=путь: окно /// открывается, снимается и закрывается. Нужен, чтобы проверять внешний вид /// воспроизводимо, а не глазами при каждом запуске. /// </summary> public static string? ScreenshotPath { get; private set; } /// <summary>Номер вкладки для снимка; задаётся ключом --tab=N.</summary> public static int? ScreenshotTab { get; private set; } /// <summary>Показать все размеры ряда поверх основной конструкции при запуске.</summary> public static bool OverlayAllSizes { get; private set; } /// <summary>Изображение, подкладываемое под чертёж при запуске.</summary> public static string? UnderlayPath { get; private set; } /// <summary>Проект, открываемый при запуске.</summary> public static string? DocumentPath { get; private set; } [STAThread] public static void Main(string[] args) { ScreenshotPath = args .FirstOrDefault(arg => arg.StartsWith("--screenshot=", StringComparison.OrdinalIgnoreCase)) ?["--screenshot=".Length..]; var tab = args.FirstOrDefault(arg => arg.StartsWith("--tab=", StringComparison.OrdinalIgnoreCase)); if (tab is not null && int.TryParse(tab["--tab=".Length..], out var index)) ScreenshotTab = index; OverlayAllSizes = args.Any( arg => arg.Equals("--overlay-sizes", StringComparison.OrdinalIgnoreCase)); UnderlayPath = args .FirstOrDefault(arg => arg.StartsWith("--underlay=", StringComparison.OrdinalIgnoreCase)) ?["--underlay=".Length..]; // Путь к проекту без ключа: так работает открытие двойным щелчком // по файлу выкройки в проводнике. DocumentPath = args.FirstOrDefault(arg => !arg.StartsWith('-') && arg.EndsWith(FileFormat.Extension, StringComparison.OrdinalIgnoreCase)); // Экспорт без открытия окна: удобно для проверки вывода и пригодится // для пакетной работы. var exportPath = args .FirstOrDefault(arg => arg.StartsWith("--export-pdf=", StringComparison.OrdinalIgnoreCase)) ?["--export-pdf=".Length..]; if (args.Any(arg => arg.Equals("--samples", StringComparison.OrdinalIgnoreCase))) { WriteSamples(args .FirstOrDefault(arg => arg.StartsWith("--out=", StringComparison.OrdinalIgnoreCase)) ?["--out=".Length..] ?? "."); return; } if (exportPath is not null) { ExportWithoutUi(exportPath); return; } BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } /// <summary> /// Записывает проверочные конструкции: плечевое изделие и головной убор. /// </summary> /// <remarks> /// Две разные конструкции нужны потому, что головной убор строится от /// других мерок и другой формы, а продукт обязан работать с обеими. /// </remarks> private static void WriteSamples(string directory) { Directory.CreateDirectory(directory); foreach (var (name, document) in new[] { ("Основа плечевого изделия", StarterDocument.Create()), ("Шапка из клиньев", HatDocument.Create()) }) { var path = Path.Combine(directory, name + FileFormat.Extension); PatternFile.Save(document, path); // Прогон через файл и обратно: проверяется, что сохранённое // открывается и даёт ту же геометрию. var reopened = PatternFile.Load(path); var report = new SeamChecker(reopened).Check(); var grading = new GradingService(reopened); var service = new PrintService(); var pdfPath = Path.ChangeExtension(path, ".pdf"); var layout = new PageLayout(PageSize.A4); var nester = new AutoNester(); var saved = 0; var sizes = reopened.Sizes.Sizes .Where(size => grading.ReportFor(size).IsReady) .Select(size => { var built = service.BuildContent(reopened, size); // Раскладка применяется к каждому размеру отдельно: детали // разных размеров имеют разные габариты, и одна раскладка // на всех была бы плоха для каждого. saved += nester.Arrange(built, layout).Saved; return (Size: size, Content: built); }) .ToArray(); var grids = new PdfExporter().ExportRange(sizes, layout, pdfPath); var sheets = grids.Sum(item => item.Grid.Count); Console.WriteLine($"{name}: построений {reopened.Draft.Count}, " + $"деталей {reopened.Pieces.Count}, " + $"размеров {reopened.Sizes.Sizes.Count}, " + $"швы {(report.IsSound ? "сходятся" : "расходятся")}, " + $"проблемных размеров {grading.ProblemSizes().Count}, " + $"листов на весь ряд {sheets}" + (saved > 0 ? $" (раскладка сэкономила {saved})" : string.Empty)); } } private static void ExportWithoutUi(string path) { var document = StarterDocument.Create(); var layout = new PageLayout(PageSize.A4); var grid = new PrintService().Export(document, layout, path); Console.WriteLine($"Выведено листов: {grid.Count} ({grid.Columns}×{grid.Rows}) → {path}"); } public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>() .UsePlatformDetect() .LogToTrace(); }