using System.Runtime.Versioning; using Kompas.Mcp.Core.Threading; using Kompas6Constants; using KompasAPI7; namespace Kompas.Mcp.Core.Drawings; /// /// Операции с 2D-чертежами через API7 (IViews). Все вызовы — на STA-потоке /// . /// [SupportedOSPlatform("windows")] public sealed class DrawingService { private readonly KompasSession _session; private readonly KompasDispatcher _dispatcher; public DrawingService(KompasSession session, KompasDispatcher dispatcher) { _session = session ?? throw new ArgumentNullException(nameof(session)); _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); } /// /// Создать на активном чертеже стандартные ассоциативные виды (спереди/сверху/слева) сохранённой /// 3D-модели (.m3d/.a3d). Главный вид привязывается к точке /// (,) листа, мм; масштаб — . /// public Task CreateStandardViewsAsync(string partFilePath, double scale, double x, double y, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => CreateStandardViewsCore(partFilePath, scale, x, y), ct); /// /// Заполнить графы основной надписи активного чертежа: обозначение (графа 2), наименование /// изделия (графа 1), материал (графа 3). Передаются только нужные поля (null/пусто — /// пропускается). Возвращает число заполненных граф. /// public Task FillTitleBlockAsync(string? designation = null, string? name = null, string? material = null, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => FillTitleBlockCore(designation, name, material), ct); private int FillTitleBlockCore(string? designation, string? name, string? material) { var fields = StampFields.Collect(designation, name, material); if (fields.Count == 0) throw new ArgumentException("Укажите хотя бы одно поле основной надписи (designation/name/material)."); var stamp = RequireActiveStamp(); foreach (var (columnId, text) in fields) stamp.Text[columnId].Str = text; // Str замещает содержимое (Clear не нужен) if (!stamp.Update()) throw new InvalidOperationException("IStamp.Update вернул FALSE — основная надпись не обновлена."); return fields.Count; } /// Прочитать текст графы основной надписи (для проверки/диагностики). public Task ReadStampCellAsync(int columnId, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var stamp = RequireActiveStamp(); return stamp.Text[columnId]?.Str ?? string.Empty; }, ct); private IStamp RequireActiveStamp() { var doc2d = RequireActiveDrawing(); var sheets = doc2d.LayoutSheets ?? throw new InvalidOperationException("Не удалось получить листы оформления чертежа."); var sheet = sheets.ItemByNumber[1] as ILayoutSheet ?? throw new InvalidOperationException("На чертеже нет листа оформления #1."); return sheet.Stamp as IStamp ?? throw new InvalidOperationException("У листа оформления нет основной надписи (Stamp)."); } // Стандартная тройка проекций: ksPtFront=1, ksPtUp=3, ksPtLeft=5 (ksRelativeProjectionTypeEnum). private static readonly object[] StandardProjections = { 1, 3, 5 }; private const int ExpectedCreated = 3; private const double GapMm = 20; // зазоры между видами (DX=DY) private DrawingViewsResult CreateStandardViewsCore(string partFilePath, double scale, double x, double y) { var path = DrawingValidation.NormalizeModelPath(partFilePath); DrawingValidation.RequireScale(scale); if (!double.IsFinite(x) || !double.IsFinite(y)) throw new ArgumentException("Координаты привязки вида должны быть конечными числами."); if (!File.Exists(path)) throw new FileNotFoundException($"Файл модели не найден: {path}", path); if (new FileInfo(path).Length == 0) throw new InvalidOperationException($"Файл модели пуст (не сохранён?): {path}"); var views = RequireActiveDrawingViews(); // Снимок номеров существующих видов — чтобы потом отличить созданные ИМЕННО этим вызовом // (на чертеже уже есть системный вид и могут быть прежние виды). var beforeNumbers = CollectViewNumbers(views); if (!views.AddStandartViews(path, "#Спереди", StandardProjections, x, y, scale, GapMm, GapMm)) throw new InvalidOperationException( $"AddStandartViews вернул FALSE — виды не созданы (файл модели или проекция недоступны): {path}"); var newViews = CollectNewViews(views, beforeNumbers); try { if (newViews.Count != ExpectedCreated) throw new InvalidOperationException( $"Ожидалось {ExpectedCreated} вида, создано {newViews.Count}."); // Гарантия непустоты: у созданных видов должна быть геометрия (иначе модель не // спроецировалась). Проверяем ИМЕННО новые виды, не всю коллекцию. if (newViews.Sum(v => v.ObjectCount) <= 0) throw new InvalidOperationException( "Стандартные виды созданы, но пусты (модель не спроецировалась). Проверьте файл модели."); return new DrawingViewsResult { Created = newViews.Count, Total = views.Count }; } catch { // Откат: удаляем виды, созданные этим вызовом, чтобы чертёж не остался в полу-состоянии. foreach (var v in newViews) try { v.Delete(); } catch { /* лучшее усилие */ } throw; } } /// /// Получить активный чертёж как . Раздельные ошибки: нет документа / /// тип != ksDocumentDrawing / не приводится к IKompasDocument2D. /// private IKompasDocument2D RequireActiveDrawing() { var doc = _session.Application.ActiveDocument ?? throw new InvalidOperationException("Нет активного документа. Создайте чертёж: document_create drawing."); if (doc.DocumentType != DocumentTypeEnum.ksDocumentDrawing) throw new InvalidOperationException( $"Активный документ не является чертежом (тип: {doc.DocumentType}). Создайте чертёж: document_create drawing."); return doc as IKompasDocument2D ?? throw new InvalidOperationException("Активный документ не приводится к IKompasDocument2D."); } private IViews RequireActiveDrawingViews() => RequireActiveDrawing().ViewsAndLayersManager?.Views ?? throw new InvalidOperationException("Не удалось получить коллекцию видов чертежа."); private static HashSet CollectViewNumbers(IViews views) { var set = new HashSet(); foreach (var o in views) if (o is IView v) set.Add(v.Number); return set; } private static List CollectNewViews(IViews views, HashSet beforeNumbers) { var list = new List(); foreach (var o in views) if (o is IView v && !beforeNumbers.Contains(v.Number)) list.Add(v); return list; } }