feat(sketch): эскиз на выбранной грани + МЦХ через API5

sketch_create_on_face: OpenSketchOnFaceAsync выбирает плоскую грань по точке
(ksPart.EntityCollection(o3d_face).SelectByPoint) и создаёт на ней эскиз —
ключевой шаг итеративного цикла «построил → выбрал грань → новый эскиз».
Общее ядро создания эскиза вынесено в CreateSketchOn.

QueryService переписан на API5 ksPart.CalcMassInertiaProperties(ST_MIX_MM|ST_MIX_KG):
- API7 IMassInertiaParam7.Calculate() непригоден — флаг Actual «залипает» после
  первого расчёта, повторный запрос отдавал устаревший объём (баг кэша);
- API5-маршрут считает по запросу и согласован с построением через ksPart;
- единицы (мм³/мм²/кг/мм) задаются битовым вектором.

Тест Sketch_on_face_adds_boss_on_top: запрос → штифт R5×H10 на верхней грани
цилиндра → повторный запрос видит прирост ≈785 мм³ (проверяет и грань, и кэш МЦХ).
Презентация: 20 инструментов, 15 тестов; OPEN_QUESTIONS обновлён.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@
This commit is contained in:
2026-05-26 09:52:56 +03:00
parent b73748f84b
commit 01f5f37c67
6 changed files with 137 additions and 54 deletions
@@ -1,5 +1,6 @@
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
using Kompas.Mcp.Core.Vision;
namespace Kompas.Mcp.Tests.Integration;
@@ -12,12 +13,14 @@ public sealed class ModelingTests
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly SnapshotService _snap;
private readonly QueryService _query;
public ModelingTests(KompasFixture fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
_snap = new SnapshotService(fx.Session, fx.Dispatcher);
_query = new QueryService(fx.Session, fx.Dispatcher);
}
[Fact]
@@ -53,6 +56,42 @@ public sealed class ModelingTests
}
}
[Fact]
public async Task Sketch_on_face_adds_boss_on_top()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Цилиндр R10×H20 на XOY → верхняя грань на Z=20.
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s1, 0, 0, 10);
await _modeler.CloseSketchAsync(s1);
await _modeler.ExtrudeAsync(s1, depth: 20);
// Запрос ДО достройки — проверяем, что повторный запрос увидит изменения
// (МЦХ не должна «залипнуть» на устаревшем значении).
var before = await _query.GetPartInfoAsync();
// Эскиз на верхней грани (точка (0,0,20)) → штифт R5×H10 наружу.
var s2 = await _modeler.OpenSketchOnFaceAsync(0, 0, 20);
await _modeler.AddCircleAsync(s2, 0, 0, 5);
await _modeler.CloseSketchAsync(s2);
var boss = await _modeler.ExtrudeAsync(s2, depth: 10);
Assert.True(boss > 0);
await _modeler.RebuildAsync();
var after = await _query.GetPartInfoAsync();
// Прирост объёма ≈ объём штифта π·5²·10 ≈ 785.4 мм³.
var deltaExpected = Math.PI * 25 * 10;
Assert.InRange(after.Volume - before.Volume, deltaExpected * 0.9, deltaExpected * 1.1);
}
finally
{
await _docs.CloseAsync(save: false);
}
}
[Fact]
public async Task Reset_invalidates_sketch_ids()
{