feat(query): перечисление граней + выбор грани по индексу

QueryService.ListFacesAsync: обход EntityCollection(o3d_face), для каждой грани —
индекс, тип поверхности (plane/cylinder/cone/sphere/torus/nurbs через ksFaceDefinition.Is*)
и площадь (GetArea, мм²). Запись FaceInfo.
PartModeler.OpenSketchOnFaceIndexAsync: эскиз на грани по индексу из list_faces —
надёжнее выбора по точке (агенту не нужно угадывать координаты).
Новые инструменты: list_faces, sketch_create_on_face_index.

Тест List_faces_and_sketch_by_index: цилиндр → 3 грани (2 plane + 1 cylinder),
площади торца ≈314 мм² и бока ≈1257 мм²; эскиз на торце по индексу → бобышка.
Презентация: 28 инструментов, 18 тестов.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@
This commit is contained in:
2026-05-26 10:08:20 +03:00
parent 8c778a449d
commit 8ac92095b6
7 changed files with 149 additions and 8 deletions
+48
View File
@@ -61,6 +61,54 @@ public sealed class QueryService
}
}, ct);
// 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).");
try
{
var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
?? throw new InvalidOperationException("Не удалось получить коллекцию граней.");
var count = faces.GetCount();
var list = new List<FaceInfo>(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";
}
/// <summary>Габаритный параллелепипед активной детали (по телам), мм.</summary>
public Task<BoundingBox> GetBoundingBoxAsync(CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>