using System.Runtime.Versioning; using Kompas.Mcp.Core.Threading; using Kompas6Constants; using KompasAPI7; namespace Kompas.Mcp.Core.Assemblies; /// /// Операции со сборками через API7 (IParts7). Аналогично HoleService/FaceEditService: /// контейнеры берутся COM-QI, все вызовы — на STA-потоке . /// [SupportedOSPlatform("windows")] public sealed class AssemblyService { private readonly KompasSession _session; private readonly KompasDispatcher _dispatcher; public AssemblyService(KompasSession session, KompasDispatcher dispatcher) { _session = session ?? throw new ArgumentNullException(nameof(session)); _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); } /// /// Вставить компонент (деталь .m3d или подсборку .a3d) из файла в активную сборку, разместив /// его origin в мировой точке (,,) мм. /// public Task AddComponentAsync(string filePath, double x, double y, double z, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => AddComponentCore(filePath, x, y, z), ct); private AddedComponent AddComponentCore(string filePath, double x, double y, double z) { var path = AssemblyValidation.NormalizeComponentPath(filePath); AssemblyValidation.RequireFinitePosition(x, y, z); if (!File.Exists(path)) throw new FileNotFoundException($"Файл компонента не найден: {path}", path); var top = RequireActiveAssembly(); var parts = top.Parts ?? throw new InvalidOperationException("Не удалось получить коллекцию компонентов сборки (Parts)."); // ExternalFile=true: компонент ссылается на внешний .m3d/.a3d. Redraw=true: вставить и // перерисовать в исходном положении (само перемещение применит UpdatePlacement ниже). var part = parts.AddFromFile(path, true, true) as IPart7 ?? throw new InvalidOperationException( $"Не удалось вставить компонент из файла (AddFromFile вернул null или не IPart7): {path}"); try { var placement = part.Placement as IPlacement3D ?? throw new InvalidOperationException("Placement компонента не приводится к IPlacement3D."); if (!placement.SetOrigin(x, y, z)) throw new InvalidOperationException("SetOrigin вернул FALSE — не удалось задать положение компонента."); // UpdatePlacement применяет изменение Placement; для вручную позиционируемого компонента // (без сопряжений/зависимостей) он может вернуть FALSE — это НЕ ошибка (нечего // пересчитывать). Фактически положение становится видимым в геометрии после RebuildModel. part.UpdatePlacement(true); top.RebuildModel(true); return new AddedComponent { Index = parts.Count - 1, Name = SafeName(part) }; } catch { // Best-effort откат осиротевшего компонента, если позиционирование не удалось. try { part.Owner?.Delete(); } catch { /* лучшее усилие */ } throw; } } /// /// Получить верхний компонент активной сборки, проверив тип документа. Раздельные ошибки: /// нет документа / документ не сборка / не 3D / нет TopPart. Деталь — тоже IKompasDocument3D, /// поэтому отсекаем по DocumentType ДО доступа к TopPart. /// private IPart7 RequireActiveAssembly() { var doc = _session.Application.ActiveDocument ?? throw new InvalidOperationException("Нет активного документа. Создайте сборку: document_create assembly."); if (doc.DocumentType != DocumentTypeEnum.ksDocumentAssembly) throw new InvalidOperationException( $"Активный документ не является сборкой (тип: {doc.DocumentType}). Создайте сборку: document_create assembly."); if (doc is not IKompasDocument3D doc3d) throw new InvalidOperationException("Активный документ не является 3D-документом."); return doc3d.TopPart as IPart7 ?? throw new InvalidOperationException("Не удалось получить TopPart сборки."); } private static string SafeName(IPart7 part) { try { return part.Name ?? string.Empty; } catch { return string.Empty; } } }