diff --git a/src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs b/src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs index 0f57fd2..e82139b 100644 --- a/src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs +++ b/src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs @@ -371,6 +371,54 @@ public sealed partial class PartModeler return id; }, ct); + /// Круговой массив: размножить операции featureIds вокруг координатной оси axis, + /// count экземпляров всего (включая исходный, ≥2), угловой шаг step (градусы) между соседними. + /// reverse — обратное направление вращения. geometric=true — быстрое геометрическое копирование. + /// Возвращает id операции. + public Task CircularPatternAsync(IReadOnlyList featureIds, CoordinateAxis axis, int count, double step, bool reverse = false, bool geometric = false, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + { + ArgumentNullException.ThrowIfNull(featureIds); + var ids = featureIds.Distinct().ToList(); + if (ids.Count == 0) throw new ArgumentException("Нужна хотя бы одна операция для массива.", nameof(featureIds)); + SketchGeometry.RequireMin(count, 2, nameof(count)); // count включает исходный + SketchGeometry.RequirePositive(step, nameof(step)); // угловой шаг > 0, ловит NaN + + // Собираем источники до мутации модели (проверка существования всех id). + var features = ids.Select(RequireFeature).ToList(); + + var part = GetTopPart(); + var axisEntity = part.GetDefaultEntity((short)CoordinateAxes.ToObj3dType(axis)) as ksEntity + ?? throw new InvalidOperationException("Не удалось получить координатную ось."); + + var entity = part.NewEntity((short)Obj3dType.o3d_circularCopy) as ksEntity + ?? throw new InvalidOperationException("NewEntity(o3d_circularCopy) вернул null."); + var def = entity.GetDefinition() as ksCircularCopyDefinition + ?? throw new InvalidOperationException("GetDefinition() массива вернул не ksCircularCopyDefinition."); + def.SetAxis(axisEntity); + // Чистый круговой массив: радиальное направление выключено (count1=1), + // экземпляры идут по кольцу (count2=count) с угловым шагом step° между соседними. + def.count1 = 1; + def.step1 = 0; + def.factor1 = false; + def.count2 = count; + def.step2 = step; + def.factor2 = false; // шаг между соседними (не общий угол) + def.inverce = reverse; + def.geomArray = geometric; + 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); + /// Перестроить документ. public Task RebuildAsync(CancellationToken ct = default) => _dispatcher.InvokeAsync(() => diff --git a/src/Kompas.Mcp.Host/Tools/FeatureTools.cs b/src/Kompas.Mcp.Host/Tools/FeatureTools.cs index b86cd3b..05662df 100644 --- a/src/Kompas.Mcp.Host/Tools/FeatureTools.cs +++ b/src/Kompas.Mcp.Host/Tools/FeatureTools.cs @@ -148,6 +148,21 @@ public sealed class FeatureTools(KompasSession session, PartModeler modeler) return $"Линейный массив создан ({count} экз. вдоль {axis}, шаг {step} мм), id={id}."; } + [McpServerTool(Name = "circular_pattern")] + [Description("Круговой массив: размножить ранее созданные операции featureIds вокруг координатной оси axis (X|Y|Z). count — количество экземпляров ВСЕГО, включая исходный (>=2). step — угловой шаг между соседними экземплярами, градусы (например 90 для 4 шт через 90°, или 120 для 3 шт). reverse=true — обратное направление вращения. geometric=true — быстрое геометрическое копирование. Возвращает id операции.")] + public async Task CircularPattern( + [Description("Id операций-источников")] int[] featureIds, + [Description("Координатная ось вращения: X|Y|Z")] string axis, + [Description("Число экземпляров всего, включая исходный (>=2)")] int count, + [Description("Угловой шаг между соседними, градусы")] double step, + [Description("Обратное направление вращения")] bool reverse = false, + [Description("Геометрическое копирование (быстрее)")] bool geometric = false) + { + await session.ConnectAsync(); + var id = await modeler.CircularPatternAsync(featureIds, CoordinateAxes.Parse(axis), count, step, reverse, geometric); + return $"Круговой массив создан ({count} экз. вокруг {axis}, шаг {step}°), id={id}."; + } + [McpServerTool(Name = "rebuild")] [Description("Перестроить активный документ.")] public async Task Rebuild() diff --git a/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs b/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs index 587f448..38d5c25 100644 --- a/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs +++ b/tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs @@ -165,6 +165,35 @@ public sealed class FeatureOpsTests finally { await _docs.CloseAsync(save: false); } } + [Fact] + public async Task CircularPattern_replicates_cylinder_around_Z() + { + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + // Цилиндр R3×H10 со смещением: центр (20,0) (объём π·9·10 ≈ 282.7 мм³). + var s = await _modeler.OpenSketchAsync(BasePlane.XOY); + await _modeler.AddCircleAsync(s, 20, 0, 3); + await _modeler.CloseSketchAsync(s); + var cyl = await _modeler.ExtrudeAsync(s, depth: 10); + var one = (await _query.GetPartInfoAsync()).Volume; + + // Круговой массив вокруг Z: 3 экз. с шагом 90° → центры (20,0),(0,20),(-20,0). + var id = await _modeler.CircularPatternAsync(new[] { cyl }, CoordinateAxis.Z, count: 3, step: 90); + Assert.True(id > 0); + await _modeler.RebuildAsync(); + + var v = (await _query.GetPartInfoAsync()).Volume; + Assert.InRange(v, one * 3 * 0.95, one * 3 * 1.05); + + // Габарит: X от -23 до 23 → 46; Y от -3 до 23 → 26 (поворот реально произошёл). + var box = await _query.GetBoundingBoxAsync(); + Assert.InRange(box.SizeX, 46 * 0.95, 46 * 1.05); + Assert.InRange(box.SizeY, 26 * 0.95, 26 * 1.05); + } + finally { await _docs.CloseAsync(save: false); } + } + [Fact] public async Task Loft_two_squares_makes_solid() {