feat: уклон граней (draft) через API5 — ksInclineDefinition o3d_incline=42

- ВОПРЕКИ прежнему предположению: уклон ЕСТЬ в API5 (операция Incline, не Draft) →
  стандартный паттерн PartModeler, регистрируется в _features (массивы/зеркало применимы)
- DraftAsync: FaceArray().Add(грани) + SetPlane(нейтральная коорд. плоскость) + angle + direction
- ЭМПИРИЧЕСКИ: direction=false=расширение, true=сужение (обратно справке) → маппинг !outward
- инструмент draft (faceIndices, neutralPlane, angle 0..90, outward); валидация угла и индексов
- интеграционный тест: 4 боковые грани коробки → усечённая пирамида (объём ±10%)
- 105 тестов; спек docs/superpowers/specs/2026-05-27-draft-design.md
This commit is contained in:
2026-05-27 10:23:16 +03:00
parent 4f7e8f3b7e
commit 205a4b02de
4 changed files with 153 additions and 0 deletions
@@ -480,6 +480,46 @@ public sealed partial class PartModeler
return id;
}, ct);
/// <summary>Уклон граней: наклонить грани faceIndices на угол angle° относительно нейтральной
/// (опорной) координатной плоскости neutralPlane — сечение тела в этой плоскости остаётся
/// неизменным. outward=true — расширение (материал добавляется), false — сужение. Грани должны
/// граничить/пересекаться с нейтральной плоскостью. Возвращает id операции.</summary>
public Task<int> DraftAsync(IReadOnlyList<int> faceIndices, BasePlane neutralPlane, double angle, bool outward = false, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
if (!double.IsFinite(angle) || angle <= 0 || angle >= 90)
throw new ArgumentOutOfRangeException(nameof(angle), "Угол уклона должен быть в (0; 90) градусов.");
ArgumentNullException.ThrowIfNull(faceIndices);
var indices = faceIndices.Distinct().ToList();
if (indices.Count == 0) throw new ArgumentException("Нужна хотя бы одна грань для уклона.", nameof(faceIndices));
var part = GetTopPart();
// Проверяем диапазон ВСЕХ индексов до мутации модели.
var faces = indices.Select(i => SelectFaceByIndex(part, i)).ToList();
var planeEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(neutralPlane)) as ksEntity
?? throw new InvalidOperationException("Не удалось получить координатную плоскость.");
var entity = part.NewEntity((short)Obj3dType.o3d_incline) as ksEntity
?? throw new InvalidOperationException("NewEntity(o3d_incline) вернул null.");
var def = entity.GetDefinition() as ksInclineDefinition
?? throw new InvalidOperationException("GetDefinition() уклона вернул не ksInclineDefinition.");
var arr = def.FaceArray() as ksEntityCollection
?? throw new InvalidOperationException("FaceArray() уклона вернул не ksEntityCollection.");
foreach (var f in faces) arr.Add(f);
def.SetPlane(planeEntity);
def.angle = angle;
def.direction = !outward; // эмпирически: direction=false → расширение, true → сужение
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(() =>
+14
View File
@@ -198,6 +198,20 @@ public sealed class FeatureTools(KompasSession session, PartModeler modeler, Hol
return $"Отверстие Ø{diameter} ({kind}) создано на грани в точке ({x}, {y}, {z}).";
}
[McpServerTool(Name = "draft")]
[Description("Уклон граней: наклонить грани faceIndices (из list_faces) на угол angle (градусы, 0..90) относительно нейтральной (опорной) координатной плоскости neutralPlane (XOY|XOZ|YOZ) — сечение тела в этой плоскости остаётся неизменным, грани уклоняются вокруг линии пересечения с ней. outward=true — расширение (материал добавляется), false — сужение. Грани должны граничить/пересекаться с нейтральной плоскостью. Возвращает id операции.")]
public async Task<string> Draft(
[Description("Индексы уклоняемых граней из list_faces")] int[] faceIndices,
[Description("Нейтральная (опорная) плоскость: XOY|XOZ|YOZ")] string neutralPlane,
[Description("Угол уклона, градусы (0..90)")] double angle,
[Description("Расширение (true) или сужение (false)")] bool outward = false)
{
await session.ConnectAsync();
var id = await modeler.DraftAsync(faceIndices, BasePlanes.Parse(neutralPlane), angle, outward);
var count = faceIndices?.Distinct().Count() ?? 0;
return $"Уклон {angle}° создан на {count} гранях (нейтральная плоскость {neutralPlane}), id={id}.";
}
[McpServerTool(Name = "rebuild")]
[Description("Перестроить активный документ.")]
public async Task<string> Rebuild()