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; }
}
}