using System.Runtime.InteropServices; using System.Runtime.Versioning; using Kompas.Mcp.Core.Threading; using Kompas6API5; using Kompas6Constants3D; namespace Kompas.Mcp.Core.Query; /// Запросы к активной 3D-модели: массово-центровочные характеристики. [SupportedOSPlatform("windows")] public sealed class QueryService { // bitVector для CalcMassInertiaProperties: ST_MIX_MM (0x1, длина — мм) | ST_MIX_KG (0x10, масса — кг). // Иначе КОМПАС вернёт значения в единицах по умолчанию (см/г). private const int MixMmKg = 0x1 | 0x10; private readonly KompasSession _session; private readonly KompasDispatcher _dispatcher; public QueryService(KompasSession session, KompasDispatcher dispatcher) { _session = session ?? throw new ArgumentNullException(nameof(session)); _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); } /// Рассчитать и вернуть МЦХ активной детали (объём, масса, площадь, центр масс). /// /// Используется API5 ksPart.CalcMassInertiaProperties — он считает МЦХ «по запросу» /// и согласован с построением детали через API5. Путь API7 IMassInertiaParam7.Calculate() /// здесь непригоден: его флаг Actual «залипает» после первого расчёта и не сбрасывается /// при изменении геометрии через API5 (повторный запрос отдавал бы устаревшее значение). /// public Task GetPartInfoAsync(CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D ?? throw new InvalidOperationException("Нет активного 3D-документа."); var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart ?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart)."); ksMassInertiaParam? mass = null; try { mass = part.CalcMassInertiaProperties(MixMmKg) as ksMassInertiaParam ?? throw new InvalidOperationException("CalcMassInertiaProperties вернул null (модель пуста или не перестроена?)."); return new PartInfo { Volume = mass.v, // объём, мм³ Mass = mass.m, // масса, кг Area = mass.F, // площадь, мм² CenterX = mass.xc, // центр масс в глобальной СК, мм CenterY = mass.yc, CenterZ = mass.zc, }; } finally { if (mass is not null && Marshal.IsComObject(mass)) Marshal.ReleaseComObject(mass); if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part); } }, ct); /// /// Перечислить компоненты верхнего уровня активной сборки (детали/подсборки): имя, /// обозначение, файл, признак детали и габарит. Обход через API7 TopPart → Parts. /// public Task> ListComponentsAsync(CancellationToken ct = default) => _dispatcher.InvokeAsync>(() => { if (_session.Application.ActiveDocument is not KompasAPI7.IKompasDocument3D doc3d) throw new InvalidOperationException("Активный документ не является 3D-документом."); if (doc3d.TopPart is not KompasAPI7.IPart7 top) throw new InvalidOperationException("Не удалось получить TopPart активного документа."); var parts = top.Parts; int count = parts.Count; var list = new List(count); for (int i = 0; i < count; i++) { if (parts.Part[i] is not KompasAPI7.IPart7 p) continue; double x1 = 0, y1 = 0, z1 = 0, x2 = 0, y2 = 0, z2 = 0; bool gab = false; try { gab = p.GetGabarit(false, false, out x1, out y1, out z1, out x2, out y2, out z2); } catch { /* габарит может быть недоступен у отдельных компонентов */ } list.Add(new ComponentInfo { Index = i, Name = SafeGet(() => p.Name), Marking = SafeGet(() => p.Marking), FileName = SafeGet(() => p.FileName), IsDetail = SafeBool(() => p.Detail), SizeX = gab ? Math.Abs(x2 - x1) : 0, SizeY = gab ? Math.Abs(y2 - y1) : 0, SizeZ = gab ? Math.Abs(z2 - z1) : 0, }); } return list; }, ct); private static string SafeGet(Func get) { try { return get() ?? string.Empty; } catch { return string.Empty; } } private static bool SafeBool(Func get) { try { return get(); } catch { return false; } } // ST_MIX_MM (0x1) — площади граней в мм². private const uint MixMm = 0x1; /// Перечислить грани активной детали: индекс, тип поверхности и площадь (мм²). public Task> ListFacesAsync(CancellationToken ct = default) => _dispatcher.InvokeAsync>(() => { var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D ?? throw new InvalidOperationException("Нет активного 3D-документа."); var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart ?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart)."); try { var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection ?? throw new InvalidOperationException("Не удалось получить коллекцию граней."); var count = faces.GetCount(); var list = new List(count); for (int i = 0; i < count; i++) { if (faces.GetByIndex(i) is not ksEntity entity) continue; if (entity.GetDefinition() is not ksFaceDefinition def) continue; list.Add(new FaceInfo { Index = i, Type = FaceType(def), Area = def.GetArea(MixMm), }); } return list; } finally { if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part); } }, ct); private static string FaceType(ksFaceDefinition def) { if (def.IsPlanar()) return "plane"; if (def.IsCylinder()) return "cylinder"; if (def.IsCone()) return "cone"; if (def.IsSphere()) return "sphere"; if (def.IsTorus()) return "torus"; if (def.IsNurbsSurface()) return "nurbs"; return "other"; } /// Перечислить рёбра активной детали: индекс, тип кривой и длину (мм). public Task> ListEdgesAsync(CancellationToken ct = default) => _dispatcher.InvokeAsync>(() => { var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D ?? throw new InvalidOperationException("Нет активного 3D-документа."); var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart ?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart)."); try { var edges = part.EntityCollection((short)Obj3dType.o3d_edge) as ksEntityCollection ?? throw new InvalidOperationException("Не удалось получить коллекцию рёбер."); var count = edges.GetCount(); var list = new List(count); for (int i = 0; i < count; i++) { if (edges.GetByIndex(i) is not ksEntity entity) continue; if (entity.GetDefinition() is not ksEdgeDefinition def) continue; list.Add(new EdgeInfo { Index = i, Type = EdgeType(def), Length = def.GetLength(MixMm), }); } return list; } finally { if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part); } }, ct); private static string EdgeType(ksEdgeDefinition def) { if (def.IsLineSeg()) return "line"; if (def.IsCircle()) return "circle"; if (def.IsArc()) return "arc"; if (def.IsEllipseArc()) return "ellipse_arc"; if (def.IsEllipse()) return "ellipse"; if (def.IsNurbs()) return "nurbs"; return "other"; } /// Габаритный параллелепипед активной детали (по телам), мм. public Task GetBoundingBoxAsync(CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D ?? throw new InvalidOperationException("Нет активного 3D-документа."); var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart ?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart)."); try { // full=false → только тела (без вспомогательной геометрии); customizable игнорируется. if (!part.GetGabarit(false, false, out double x1, out double y1, out double z1, out double x2, out double y2, out double z2)) throw new InvalidOperationException("GetGabarit вернул FALSE (модель пуста?)."); return new BoundingBox { MinX = Math.Min(x1, x2), MinY = Math.Min(y1, y2), MinZ = Math.Min(z1, z2), MaxX = Math.Max(x1, x2), MaxY = Math.Max(y1, y2), MaxZ = Math.Max(z1, z2), }; } finally { if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part); } }, ct); }