refactor(core): унификация COM-хелперов и классификаторов топологии
Вынесены общие ComHelper (Release + Safe-геттеры) и GeomClassifiers (FaceType/EdgeType), устранены дубли в 5+ сервисах. Освобождение doc3d RCW в GetTopPart, void-возвраты вместо фиктивных bool, HashSet для OpKinds, DocumentTypeName в паспорте модели, FileExtension без аллокаций. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using Kompas6API5;
|
||||
|
||||
namespace Kompas.Mcp.Core.Query;
|
||||
|
||||
/// <summary>
|
||||
/// Классификаторы топологических объектов КОМПАС: строковый тип грани и ребра.
|
||||
/// <para>
|
||||
/// Каждый вызов COM-метода обёрнут в отдельный <c>try/catch</c> — на экзотической геометрии
|
||||
/// (NURBS, импортированные STEP-тела) методы класса <c>ksFaceDefinition</c>/<c>ksEdgeDefinition</c>
|
||||
/// могут бросить <c>COMException</c>, что не должно прерывать bulk-перебор. Ранее этот код
|
||||
/// был продублирован в <c>QueryService</c> (без защиты) и <c>ModelInspectionService</c> (с защитой).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal static class GeomClassifiers
|
||||
{
|
||||
/// <summary>Строковый тип поверхности: plane/cylinder/cone/sphere/torus/nurbs/other.</summary>
|
||||
internal static string FaceType(ksFaceDefinition d)
|
||||
{
|
||||
try { if (d.IsPlanar()) return "plane"; } catch { }
|
||||
try { if (d.IsCylinder()) return "cylinder"; } catch { }
|
||||
try { if (d.IsCone()) return "cone"; } catch { }
|
||||
try { if (d.IsSphere()) return "sphere"; } catch { }
|
||||
try { if (d.IsTorus()) return "torus"; } catch { }
|
||||
try { if (d.IsNurbsSurface()) return "nurbs"; } catch { }
|
||||
return "other";
|
||||
}
|
||||
|
||||
/// <summary>Строковый тип кривой: line/circle/arc/ellipse_arc/ellipse/nurbs/other.</summary>
|
||||
internal static string EdgeType(ksEdgeDefinition d)
|
||||
{
|
||||
try { if (d.IsLineSeg()) return "line"; } catch { }
|
||||
try { if (d.IsCircle()) return "circle"; } catch { }
|
||||
try { if (d.IsArc()) return "arc"; } catch { }
|
||||
try { if (d.IsEllipseArc()) return "ellipse_arc"; } catch { }
|
||||
try { if (d.IsEllipse()) return "ellipse"; } catch { }
|
||||
try { if (d.IsNurbs()) return "nurbs"; } catch { }
|
||||
return "other";
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,9 @@ public static class InspectionText
|
||||
}
|
||||
|
||||
// Формообразующие операции, наличие которых означает параметрическую историю.
|
||||
private static readonly string[] OpKinds =
|
||||
{ "sketch", "extrusion", "cut_extrusion", "revolve", "cut_revolve", "fillet", "chamfer" };
|
||||
// HashSet<string> даёт O(1) Contains вместо O(n) по массиву при bulk-проверке граней.
|
||||
private static readonly HashSet<string> OpKinds =
|
||||
new(StringComparer.Ordinal) { "sketch", "extrusion", "cut_extrusion", "revolve", "cut_revolve", "fillet", "chamfer" };
|
||||
|
||||
/// <summary>
|
||||
/// Модель — «голый» импорт без истории построения (типично для STEP): в дереве только начало
|
||||
@@ -39,18 +40,21 @@ public static class InspectionText
|
||||
public static string GroupTypes(IReadOnlyList<string> types)
|
||||
{
|
||||
if (types.Count == 0) return "—";
|
||||
// Count материализуем один раз: IGrouping<K,V> — IEnumerable без кеша,
|
||||
// двойной вызов g.Count() итерирует группу дважды.
|
||||
return string.Join(", ", types
|
||||
.GroupBy(t => t)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.ThenBy(g => g.Key, StringComparer.Ordinal)
|
||||
.Select(g => $"{g.Count()} {g.Key}"));
|
||||
.Select(g => (Key: g.Key, Count: g.Count()))
|
||||
.OrderByDescending(x => x.Count)
|
||||
.ThenBy(x => x.Key, StringComparer.Ordinal)
|
||||
.Select(x => $"{x.Count} {x.Key}"));
|
||||
}
|
||||
|
||||
/// <summary>Отрендерить «паспорт» модели в читаемый текст.</summary>
|
||||
public static string Render(ModelDescription d)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Документ: «{d.Name}» (деталь)");
|
||||
sb.AppendLine($"Документ: «{d.Name}» ({d.DocumentTypeName})");
|
||||
|
||||
if (d.IsEmpty)
|
||||
{
|
||||
|
||||
@@ -38,4 +38,10 @@ public sealed record ModelDescription
|
||||
|
||||
/// <summary>Переменные модели.</summary>
|
||||
public required IReadOnlyList<VariableInfo> Variables { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Локализованное название типа документа для вывода: «деталь», «сборка», «чертёж» и т.д.
|
||||
/// По умолчанию «деталь».
|
||||
/// </summary>
|
||||
public string DocumentTypeName { get; init; } = "деталь";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using Kompas.Mcp.Core.Documents;
|
||||
using Kompas.Mcp.Core.Interop;
|
||||
using Kompas.Mcp.Core.Threading;
|
||||
using Kompas6API5;
|
||||
using Kompas6Constants3D;
|
||||
@@ -34,7 +36,7 @@ public sealed class ModelInspectionService
|
||||
var part = GetTopPart();
|
||||
try
|
||||
{
|
||||
var name = ActiveDocName();
|
||||
var (name, typeName) = ActiveDocInfo();
|
||||
|
||||
bool hasGab = part.GetGabarit(false, false,
|
||||
out double x1, out double y1, out double z1, out double x2, out double y2, out double z2);
|
||||
@@ -48,6 +50,7 @@ public sealed class ModelInspectionService
|
||||
Bodies = Array.Empty<BodyInfo>(), FaceTypes = Array.Empty<string>(),
|
||||
EdgeTypes = Array.Empty<string>(), Features = Array.Empty<FeatureInfo>(),
|
||||
Imported = false, Variables = Array.Empty<VariableInfo>(),
|
||||
DocumentTypeName = typeName,
|
||||
};
|
||||
|
||||
var bodies = ReadBodies(part);
|
||||
@@ -67,9 +70,10 @@ public sealed class ModelInspectionService
|
||||
Features = features,
|
||||
Imported = InspectionText.IsImported(features, bodies.Count),
|
||||
Variables = variables,
|
||||
DocumentTypeName = typeName,
|
||||
};
|
||||
}
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
/// <summary>Перечислить узлы дерева построения по порядку.</summary>
|
||||
@@ -78,7 +82,7 @@ public sealed class ModelInspectionService
|
||||
{
|
||||
var part = GetTopPart();
|
||||
try { return ReadFeatures(part); }
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
/// <summary>Перечислить тела детали.</summary>
|
||||
@@ -87,7 +91,7 @@ public sealed class ModelInspectionService
|
||||
{
|
||||
var part = GetTopPart();
|
||||
try { return ReadBodies(part); }
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
/// <summary>Перечислить переменные модели.</summary>
|
||||
@@ -96,7 +100,7 @@ public sealed class ModelInspectionService
|
||||
{
|
||||
var part = GetTopPart();
|
||||
try { return ReadVariables(part); }
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
/// <summary>Детали грани по индексу из list_faces: тип, площадь, нормаль, радиус, число рёбер.</summary>
|
||||
@@ -117,14 +121,14 @@ public sealed class ModelInspectionService
|
||||
return new FaceDetail
|
||||
{
|
||||
Index = index,
|
||||
Type = FaceType(def),
|
||||
Type = GeomClassifiers.FaceType(def),
|
||||
Area = area,
|
||||
Normal = FaceNormal(def),
|
||||
Radius = radius,
|
||||
EdgeCount = edgeCount,
|
||||
};
|
||||
}
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
/// <summary>Детали ребра по индексу из list_edges: тип, длина, смежные грани, концевые вершины.</summary>
|
||||
@@ -139,13 +143,13 @@ public sealed class ModelInspectionService
|
||||
|
||||
double len = 0; try { len = def.GetLength(MixMm); } catch { }
|
||||
string? f1 = null, f2 = null;
|
||||
try { if (def.GetAdjacentFace(true) is ksFaceDefinition a) f1 = FaceType(a); } catch { }
|
||||
try { if (def.GetAdjacentFace(false) is ksFaceDefinition b) f2 = FaceType(b); } catch { }
|
||||
try { if (def.GetAdjacentFace(true) is ksFaceDefinition a) f1 = GeomClassifiers.FaceType(a); } catch { }
|
||||
try { if (def.GetAdjacentFace(false) is ksFaceDefinition b) f2 = GeomClassifiers.FaceType(b); } catch { }
|
||||
|
||||
return new EdgeDetail
|
||||
{
|
||||
Index = index,
|
||||
Type = EdgeType(def),
|
||||
Type = GeomClassifiers.EdgeType(def),
|
||||
Length = len,
|
||||
AdjacentFace1 = f1,
|
||||
AdjacentFace2 = f2,
|
||||
@@ -153,7 +157,7 @@ public sealed class ModelInspectionService
|
||||
End = Vertex(def, false),
|
||||
};
|
||||
}
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
/// <summary>
|
||||
@@ -186,7 +190,7 @@ public sealed class ModelInspectionService
|
||||
AngleValid = angleValid,
|
||||
};
|
||||
}
|
||||
finally { Release(part); }
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
// ── чтение данных (на STA-потоке) ─────────────────────────────────────
|
||||
@@ -264,11 +268,11 @@ public sealed class ModelInspectionService
|
||||
if (vc.GetByIndex(i) is not ksVariable v) continue;
|
||||
list.Add(new VariableInfo
|
||||
{
|
||||
Name = SafeStr(() => v.name),
|
||||
Expression = SafeStr(() => v.Expression),
|
||||
Value = SafeNum(() => v.value),
|
||||
External = SafeBool(() => v.external),
|
||||
Information = SafeBool(() => v.Information),
|
||||
Name = ComHelper.SafeStr(() => v.name),
|
||||
Expression = ComHelper.SafeStr(() => v.Expression),
|
||||
Value = ComHelper.SafeNum(() => v.value),
|
||||
External = ComHelper.SafeBool(() => v.external),
|
||||
Information = ComHelper.SafeBool(() => v.Information),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
@@ -280,10 +284,12 @@ public sealed class ModelInspectionService
|
||||
var edgeTypes = new List<string>();
|
||||
if (Faces(part) is { } faces)
|
||||
for (int i = 0; i < faces.GetCount(); i++)
|
||||
if (faces.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksFaceDefinition d) faceTypes.Add(FaceType(d));
|
||||
if (faces.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksFaceDefinition d)
|
||||
faceTypes.Add(GeomClassifiers.FaceType(d));
|
||||
if (Edges(part) is { } edges)
|
||||
for (int i = 0; i < edges.GetCount(); i++)
|
||||
if (edges.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksEdgeDefinition d) edgeTypes.Add(EdgeType(d));
|
||||
if (edges.GetByIndex(i) is ksEntity e && e.GetDefinition() is ksEdgeDefinition d)
|
||||
edgeTypes.Add(GeomClassifiers.EdgeType(d));
|
||||
return (faceTypes, edgeTypes);
|
||||
}
|
||||
|
||||
@@ -349,16 +355,43 @@ public sealed class ModelInspectionService
|
||||
{
|
||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
||||
return doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
||||
// Освобождаем RCW документа сразу после получения ksPart — part имеет собственный ref.
|
||||
try
|
||||
{
|
||||
return doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
||||
}
|
||||
finally { ComHelper.Release(doc3d); }
|
||||
}
|
||||
|
||||
private string ActiveDocName()
|
||||
/// <summary>Имя и локализованный тип активного документа. Отдельный API7-вызов от GetTopPart.</summary>
|
||||
private (string name, string typeName) ActiveDocInfo()
|
||||
{
|
||||
var doc = _session.Application.ActiveDocument;
|
||||
try { return doc?.Name ?? ""; }
|
||||
catch { return ""; }
|
||||
finally { Release(doc); }
|
||||
try
|
||||
{
|
||||
string name = ComHelper.SafeStr(() => doc?.Name);
|
||||
string typeName = "деталь";
|
||||
if (doc is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
typeName = DocumentTypes.FromEnum(doc.DocumentType) switch
|
||||
{
|
||||
KompasDocumentType.Assembly => "сборка",
|
||||
KompasDocumentType.Drawing => "чертёж",
|
||||
KompasDocumentType.Fragment => "фрагмент",
|
||||
KompasDocumentType.Specification => "спецификация",
|
||||
KompasDocumentType.Text => "текст",
|
||||
_ => "деталь",
|
||||
};
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return (name, typeName);
|
||||
}
|
||||
catch { return ("", "деталь"); }
|
||||
finally { ComHelper.Release(doc); }
|
||||
}
|
||||
|
||||
private static ksEntityCollection? Faces(ksPart part) => part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection;
|
||||
@@ -382,44 +415,11 @@ public sealed class ModelInspectionService
|
||||
?? throw new InvalidOperationException($"Ребро[{index}] не приводится к ksEdgeDefinition.");
|
||||
}
|
||||
|
||||
private static string FaceType(ksFaceDefinition d)
|
||||
{
|
||||
try { if (d.IsPlanar()) return "plane"; } catch { }
|
||||
try { if (d.IsCylinder()) return "cylinder"; } catch { }
|
||||
try { if (d.IsCone()) return "cone"; } catch { }
|
||||
try { if (d.IsSphere()) return "sphere"; } catch { }
|
||||
try { if (d.IsTorus()) return "torus"; } catch { }
|
||||
try { if (d.IsNurbsSurface()) return "nurbs"; } catch { }
|
||||
return "other";
|
||||
}
|
||||
|
||||
private static string EdgeType(ksEdgeDefinition d)
|
||||
{
|
||||
try { if (d.IsLineSeg()) return "line"; } catch { }
|
||||
try { if (d.IsCircle()) return "circle"; } catch { }
|
||||
try { if (d.IsArc()) return "arc"; } catch { }
|
||||
try { if (d.IsEllipseArc()) return "ellipse_arc"; } catch { }
|
||||
try { if (d.IsEllipse()) return "ellipse"; } catch { }
|
||||
try { if (d.IsNurbs()) return "nurbs"; } catch { }
|
||||
return "other";
|
||||
}
|
||||
|
||||
private static BoundingBox Box(double x1, double y1, double z1, double x2, double y2, double z2) => new()
|
||||
{
|
||||
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),
|
||||
};
|
||||
|
||||
private static string SafeStr(Func<string?> g) { try { return g() ?? ""; } catch { return ""; } }
|
||||
private static double SafeNum(Func<double> g) { try { return g(); } catch { return 0; } }
|
||||
private static bool SafeBool(Func<bool> g) { try { return g(); } catch { return false; } }
|
||||
private static string Inv(FormattableString fs) => FormattableString.Invariant(fs);
|
||||
|
||||
private static void Release(object? com)
|
||||
{
|
||||
if (com is not null && Marshal.IsComObject(com))
|
||||
{
|
||||
try { Marshal.ReleaseComObject(com); } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using Kompas.Mcp.Core.Interop;
|
||||
using Kompas.Mcp.Core.Threading;
|
||||
using Kompas6API5;
|
||||
using Kompas6Constants3D;
|
||||
@@ -14,6 +15,9 @@ public sealed class QueryService
|
||||
// Иначе КОМПАС вернёт значения в единицах по умолчанию (см/г).
|
||||
private const int MixMmKg = 0x1 | 0x10;
|
||||
|
||||
// ST_MIX_MM (0x1) — площади граней / длины рёбер в мм.
|
||||
private const uint MixMm = 0x1;
|
||||
|
||||
private readonly KompasSession _session;
|
||||
private readonly KompasDispatcher _dispatcher;
|
||||
|
||||
@@ -33,11 +37,7 @@ public sealed class QueryService
|
||||
public Task<PartInfo> 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).");
|
||||
|
||||
var part = GetTopPart();
|
||||
ksMassInertiaParam? mass = null;
|
||||
try
|
||||
{
|
||||
@@ -56,8 +56,8 @@ public sealed class QueryService
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (mass is not null && Marshal.IsComObject(mass)) Marshal.ReleaseComObject(mass);
|
||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
||||
ComHelper.Release(mass);
|
||||
ComHelper.Release(part);
|
||||
}
|
||||
}, ct);
|
||||
|
||||
@@ -83,13 +83,22 @@ public sealed class QueryService
|
||||
bool gab = false;
|
||||
try { gab = p.GetGabarit(false, false, out x1, out y1, out z1, out x2, out y2, out z2); }
|
||||
catch { /* габарит может быть недоступен у отдельных компонентов */ }
|
||||
|
||||
// Inline safe-геттеры: избегаем лямбда-аллокаций в горячем цикле по компонентам.
|
||||
string name = "", marking = "", fileName = "";
|
||||
bool isDetail = false;
|
||||
try { name = p.Name ?? ""; } catch { }
|
||||
try { marking = p.Marking ?? ""; } catch { }
|
||||
try { fileName = p.FileName ?? ""; } catch { }
|
||||
try { isDetail = p.Detail; } catch { }
|
||||
|
||||
list.Add(new ComponentInfo
|
||||
{
|
||||
Index = i,
|
||||
Name = SafeGet(() => p.Name),
|
||||
Marking = SafeGet(() => p.Marking),
|
||||
FileName = SafeGet(() => p.FileName),
|
||||
IsDetail = SafeBool(() => p.Detail),
|
||||
Name = name,
|
||||
Marking = marking,
|
||||
FileName = fileName,
|
||||
IsDetail = isDetail,
|
||||
SizeX = gab ? Math.Abs(x2 - x1) : 0,
|
||||
SizeY = gab ? Math.Abs(y2 - y1) : 0,
|
||||
SizeZ = gab ? Math.Abs(z2 - z1) : 0,
|
||||
@@ -98,27 +107,11 @@ public sealed class QueryService
|
||||
return list;
|
||||
}, ct);
|
||||
|
||||
private static string SafeGet(Func<string?> get)
|
||||
{
|
||||
try { return get() ?? string.Empty; } catch { return string.Empty; }
|
||||
}
|
||||
|
||||
private static bool SafeBool(Func<bool> get)
|
||||
{
|
||||
try { return get(); } catch { return false; }
|
||||
}
|
||||
|
||||
// ST_MIX_MM (0x1) — площади граней в мм².
|
||||
private const uint MixMm = 0x1;
|
||||
|
||||
/// <summary>Перечислить грани активной детали: индекс, тип поверхности и площадь (мм²).</summary>
|
||||
public Task<IReadOnlyList<FaceInfo>> ListFacesAsync(CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync<IReadOnlyList<FaceInfo>>(() =>
|
||||
{
|
||||
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).");
|
||||
var part = GetTopPart();
|
||||
try
|
||||
{
|
||||
var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
|
||||
@@ -133,37 +126,20 @@ public sealed class QueryService
|
||||
list.Add(new FaceInfo
|
||||
{
|
||||
Index = i,
|
||||
Type = FaceType(def),
|
||||
Type = GeomClassifiers.FaceType(def),
|
||||
Area = def.GetArea(MixMm),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
||||
}
|
||||
finally { ComHelper.Release(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";
|
||||
}
|
||||
|
||||
/// <summary>Перечислить рёбра активной детали: индекс, тип кривой и длину (мм).</summary>
|
||||
public Task<IReadOnlyList<EdgeInfo>> ListEdgesAsync(CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync<IReadOnlyList<EdgeInfo>>(() =>
|
||||
{
|
||||
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).");
|
||||
var part = GetTopPart();
|
||||
try
|
||||
{
|
||||
var edges = part.EntityCollection((short)Obj3dType.o3d_edge) as ksEntityCollection
|
||||
@@ -178,37 +154,20 @@ public sealed class QueryService
|
||||
list.Add(new EdgeInfo
|
||||
{
|
||||
Index = i,
|
||||
Type = EdgeType(def),
|
||||
Type = GeomClassifiers.EdgeType(def),
|
||||
Length = def.GetLength(MixMm),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
||||
}
|
||||
finally { ComHelper.Release(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";
|
||||
}
|
||||
|
||||
/// <summary>Габаритный параллелепипед активной детали (по телам), мм.</summary>
|
||||
public Task<BoundingBox> 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).");
|
||||
var part = GetTopPart();
|
||||
try
|
||||
{
|
||||
// full=false → только тела (без вспомогательной геометрии); customizable игнорируется.
|
||||
@@ -223,9 +182,24 @@ public sealed class QueryService
|
||||
MaxX = Math.Max(x1, x2), MaxY = Math.Max(y1, y2), MaxZ = Math.Max(z1, z2),
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Marshal.IsComObject(part)) Marshal.ReleaseComObject(part);
|
||||
}
|
||||
finally { ComHelper.Release(part); }
|
||||
}, ct);
|
||||
|
||||
// ── низкоуровневые помощники ──────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Получить вершинный компонент активной детали. Освобождает RCW doc3d сразу после
|
||||
/// GetPart — ksPart имеет собственный COM-счётчик ссылок.
|
||||
/// </summary>
|
||||
private ksPart GetTopPart()
|
||||
{
|
||||
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
||||
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
||||
try
|
||||
{
|
||||
return doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
||||
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
||||
}
|
||||
finally { ComHelper.Release(doc3d); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user