feat: зеркало операций и тела (mirror_operation, mirror_body)
- MirrorOperationAsync: ksMirrorCopyDefinition o3d_mirrorOperation, SetPlane + GetOperationArray - MirrorBodyAsync: ksMirrorCopyAllDefinition o3d_mirrorAllOperation, SetPlane + ChooseBodies().ChooseBodiesType=ksAllBodies (отражает все тела, оригинал сохраняется) - инструменты mirror_operation (featureIds, plane), mirror_body (plane) - интеграционные тесты: mirror_operation на ДВУХ источниках (коллекция), mirror_body — оба ±5% - пакет C завершён: 62 инструмента, 100 тестов (60 unit + 40 integration)
This commit is contained in:
@@ -419,6 +419,67 @@ public sealed partial class PartModeler
|
||||
return id;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Зеркальная копия операций featureIds относительно координатной плоскости plane.
|
||||
/// Возвращает id операции.</summary>
|
||||
public Task<int> MirrorOperationAsync(IReadOnlyList<int> featureIds, BasePlane plane, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(featureIds);
|
||||
var ids = featureIds.Distinct().ToList();
|
||||
if (ids.Count == 0) throw new ArgumentException("Нужна хотя бы одна операция для зеркала.", nameof(featureIds));
|
||||
|
||||
// Собираем источники до мутации модели (проверка существования всех id).
|
||||
var features = ids.Select(RequireFeature).ToList();
|
||||
|
||||
var part = GetTopPart();
|
||||
var planeEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(plane)) as ksEntity
|
||||
?? throw new InvalidOperationException("Не удалось получить координатную плоскость.");
|
||||
|
||||
var entity = part.NewEntity((short)Obj3dType.o3d_mirrorOperation) as ksEntity
|
||||
?? throw new InvalidOperationException("NewEntity(o3d_mirrorOperation) вернул null.");
|
||||
var def = entity.GetDefinition() as ksMirrorCopyDefinition
|
||||
?? throw new InvalidOperationException("GetDefinition() зеркала вернул не ksMirrorCopyDefinition.");
|
||||
def.SetPlane(planeEntity);
|
||||
var arr = def.GetOperationArray() as ksEntityCollection
|
||||
?? throw new InvalidOperationException("GetOperationArray() зеркала вернул не ksEntityCollection.");
|
||||
foreach (var f in features) arr.Add(f);
|
||||
|
||||
if (!entity.Create())
|
||||
throw new InvalidOperationException(
|
||||
"Create() зеркальной копии вернул FALSE (копия пересекает исходную геометрию?).");
|
||||
|
||||
var id = _nextId++;
|
||||
_features[id] = entity;
|
||||
return id;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Зеркально отразить всё тело относительно координатной плоскости plane (получить
|
||||
/// симметричную деталь: оригинал + зеркало). Возвращает id операции.</summary>
|
||||
public Task<int> MirrorBodyAsync(BasePlane plane, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var part = GetTopPart();
|
||||
var planeEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(plane)) as ksEntity
|
||||
?? throw new InvalidOperationException("Не удалось получить координатную плоскость.");
|
||||
|
||||
var entity = part.NewEntity((short)Obj3dType.o3d_mirrorAllOperation) as ksEntity
|
||||
?? throw new InvalidOperationException("NewEntity(o3d_mirrorAllOperation) вернул null.");
|
||||
var def = entity.GetDefinition() as ksMirrorCopyAllDefinition
|
||||
?? throw new InvalidOperationException("GetDefinition() зеркала тела вернул не ksMirrorCopyAllDefinition.");
|
||||
def.SetPlane(planeEntity);
|
||||
// Отражаем все тела детали.
|
||||
if (def.ChooseBodies() is ksChooseBodies chooser)
|
||||
chooser.ChooseBodiesType = (int)ksChooseBodiesType.ksAllBodies;
|
||||
|
||||
if (!entity.Create())
|
||||
throw new InvalidOperationException(
|
||||
"Create() зеркального отражения тела вернул FALSE (тело пересекает плоскость симметрии?).");
|
||||
|
||||
var id = _nextId++;
|
||||
_features[id] = entity;
|
||||
return id;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Перестроить документ.</summary>
|
||||
public Task RebuildAsync(CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
|
||||
@@ -163,6 +163,27 @@ public sealed class FeatureTools(KompasSession session, PartModeler modeler)
|
||||
return $"Круговой массив создан ({count} экз. вокруг {axis}, шаг {step}°), id={id}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mirror_operation")]
|
||||
[Description("Зеркальная копия операций: отразить ранее созданные операции featureIds относительно координатной плоскости plane (XOY|XOZ|YOZ). Добавляет зеркальные копии (оригинал сохраняется). Возвращает id операции.")]
|
||||
public async Task<string> MirrorOperation(
|
||||
[Description("Id операций-источников")] int[] featureIds,
|
||||
[Description("Плоскость симметрии: XOY|XOZ|YOZ")] string plane)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var id = await modeler.MirrorOperationAsync(featureIds, BasePlanes.Parse(plane));
|
||||
return $"Зеркальная копия операций создана (плоскость {plane}), id={id}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "mirror_body")]
|
||||
[Description("Зеркально отразить всё тело детали относительно координатной плоскости plane (XOY|XOZ|YOZ) — получить симметричную деталь (оригинал + зеркало). Удобно для моделирования половины с последующим отражением. Возвращает id операции.")]
|
||||
public async Task<string> MirrorBody(
|
||||
[Description("Плоскость симметрии: XOY|XOZ|YOZ")] string plane)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var id = await modeler.MirrorBodyAsync(BasePlanes.Parse(plane));
|
||||
return $"Тело зеркально отражено (плоскость {plane}), id={id}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "rebuild")]
|
||||
[Description("Перестроить активный документ.")]
|
||||
public async Task<string> Rebuild()
|
||||
|
||||
@@ -194,6 +194,67 @@ public sealed class FeatureOpsTests
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MirrorOperation_mirrors_two_features_across_YOZ()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Две отдельные операции (проверяем заполнение коллекции несколькими источниками):
|
||||
// цилиндр R3 центр (20,0) и цилиндр R2 центр (10,0), оба H10.
|
||||
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddCircleAsync(s1, 20, 0, 3);
|
||||
await _modeler.CloseSketchAsync(s1);
|
||||
var c1 = await _modeler.ExtrudeAsync(s1, depth: 10);
|
||||
|
||||
var s2 = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddCircleAsync(s2, 10, 0, 2);
|
||||
await _modeler.CloseSketchAsync(s2);
|
||||
var c2 = await _modeler.ExtrudeAsync(s2, depth: 10);
|
||||
var before = (await _query.GetPartInfoAsync()).Volume; // π·9·10 + π·4·10 ≈ 408.4
|
||||
|
||||
// Зеркало обеих операций относительно YOZ (x=0) → копии в (-20,0),(-10,0).
|
||||
var id = await _modeler.MirrorOperationAsync(new[] { c1, c2 }, BasePlane.YOZ);
|
||||
Assert.True(id > 0);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // удвоение
|
||||
Assert.InRange(v, before * 2 * 0.95, before * 2 * 1.05);
|
||||
|
||||
// Габарит X симметричен: от -23 до 23 → 46.
|
||||
var box = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(box.SizeX, 46 * 0.95, 46 * 1.05);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MirrorBody_makes_symmetric_part_across_YOZ()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Цилиндр R3×H10 центр (20,0) (объём ≈ 282.7 мм³).
|
||||
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddCircleAsync(s, 20, 0, 3);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 10);
|
||||
var before = (await _query.GetPartInfoAsync()).Volume;
|
||||
|
||||
// Зеркально отразить тело относительно YOZ → оригинал + зеркало в (-20,0).
|
||||
var id = await _modeler.MirrorBodyAsync(BasePlane.YOZ);
|
||||
Assert.True(id > 0);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||||
Assert.InRange(v, before * 2 * 0.95, before * 2 * 1.05);
|
||||
|
||||
var box = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(box.SizeX, 46 * 0.95, 46 * 1.05); // от -23 до 23
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Loft_two_squares_makes_solid()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user