Files
kompas3d-mcp/src/Kompas.Mcp.Host/Tools/FeatureTools.cs
T
mikhail 675e43160e feat: коническое отверстие (hole_conic) — ksHTConic + IConicHoleParameters
- ConicHoleAsync (ksHTConic + IConicHoleParameters: ConicType=ksCNAngle, ConicAngle)
- инструмент hole_conic (diameter у дна, conicAngle 0..180); тот же HoleCore-паттерн
- интеграционный тест (конус удаляет больше цилиндра Ø10); типы отверстий завершены
- 70 инструментов, 119 тестов
2026-05-27 12:17:28 +03:00

271 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using Kompas.Mcp.Core;
using Kompas.Mcp.Core.Modeling;
using ModelContextProtocol.Server;
namespace Kompas.Mcp.Host.Tools;
/// <summary>Инструменты формообразующих операций.</summary>
[McpServerToolType]
public sealed class FeatureTools(KompasSession session, PartModeler modeler, HoleService holes)
{
[McpServerTool(Name = "extrude_boss")]
[Description("Выдавить эскиз, добавив материал (на глубину depth, мм). Возвращает id операции.")]
public async Task<string> ExtrudeBoss(int sketchId, double depth, [Description("Прямое направление")] bool forward = true)
{
await session.ConnectAsync();
var id = await modeler.ExtrudeAsync(sketchId, depth, cut: false, forward: forward);
return $"Выдавливание (boss) создано, id={id}.";
}
[McpServerTool(Name = "extrude_cut")]
[Description("Вырезать выдавливанием по эскизу. Для сквозного отверстия передайте throughAll=true (depth не нужен). Иначе вырез на глубину depth (мм).")]
public async Task<string> ExtrudeCut(
int sketchId,
[Description("Глубина, мм (нужна, если throughAll=false)")] double depth = 0,
[Description("Вырезать насквозь через всю деталь")] bool throughAll = false,
[Description("Прямое направление")] bool forward = true)
{
await session.ConnectAsync();
var id = await modeler.ExtrudeAsync(sketchId, depth, cut: true, forward: forward, throughAll: throughAll);
return $"Вырез (cut) создан, id={id}.";
}
[McpServerTool(Name = "revolve_boss")]
[Description("Вращать эскиз вокруг его осевой линии, добавив материал. Эскиз должен содержать профиль и осевую линию (sketch_add_axis). angle — угол в градусах (по умолчанию 360). Возвращает id операции.")]
public async Task<string> RevolveBoss(
int sketchId,
[Description("Угол вращения, градусы (0..360]")] double angle = 360,
[Description("Прямое направление")] bool forward = true)
{
await session.ConnectAsync();
var id = await modeler.RevolveAsync(sketchId, angle, cut: false, forward: forward);
return $"Вращение (boss) создано, id={id}.";
}
[McpServerTool(Name = "revolve_cut")]
[Description("Вырезать вращением эскиза вокруг его осевой линии. Эскиз должен содержать профиль и осевую линию (sketch_add_axis). angle — угол в градусах (по умолчанию 360). Возвращает id операции.")]
public async Task<string> RevolveCut(
int sketchId,
[Description("Угол вращения, градусы (0..360]")] double angle = 360,
[Description("Прямое направление")] bool forward = true)
{
await session.ConnectAsync();
var id = await modeler.RevolveAsync(sketchId, angle, cut: true, forward: forward);
return $"Вырез вращением (cut) создан, id={id}.";
}
[McpServerTool(Name = "fillet_edge")]
[Description("Скруглить ребро детали, выбранное по точке (мировые координаты x,y,z в мм, точка должна лежать на ребре), радиусом radius (мм). Сделайте model_snapshot, прикиньте точку на ребре. Возвращает id операции.")]
public async Task<string> FilletEdge(double x, double y, double z, double radius)
{
await session.ConnectAsync();
var id = await modeler.FilletEdgeAsync(x, y, z, radius);
return $"Скругление R{radius} создано на ребре в точке ({x}, {y}, {z}), id={id}.";
}
[McpServerTool(Name = "chamfer_edge")]
[Description("Снять фаску с ребра детали, выбранного по точке (мировые координаты x,y,z в мм, точка должна лежать на ребре), катетом distance (мм). Сделайте model_snapshot, прикиньте точку на ребре. Возвращает id операции.")]
public async Task<string> ChamferEdge(double x, double y, double z, double distance)
{
await session.ConnectAsync();
var id = await modeler.ChamferEdgeAsync(x, y, z, distance);
return $"Фаска {distance}×{distance} создана на ребре в точке ({x}, {y}, {z}), id={id}.";
}
[McpServerTool(Name = "fillet_edge_index")]
[Description("Скруглить ребро детали по индексу из list_edges радиусом radius (мм). Надёжнее выбора по точке. Возвращает id операции.")]
public async Task<string> FilletEdgeIndex([Description("Индекс ребра из list_edges")] int edgeIndex, double radius)
{
await session.ConnectAsync();
var id = await modeler.FilletEdgeIndexAsync(edgeIndex, radius);
return $"Скругление R{radius} создано на ребре [{edgeIndex}], id={id}.";
}
[McpServerTool(Name = "chamfer_edge_index")]
[Description("Снять фаску с ребра детали по индексу из list_edges катетом distance (мм). Надёжнее выбора по точке. Возвращает id операции.")]
public async Task<string> ChamferEdgeIndex([Description("Индекс ребра из list_edges")] int edgeIndex, double distance)
{
await session.ConnectAsync();
var id = await modeler.ChamferEdgeIndexAsync(edgeIndex, distance);
return $"Фаска {distance}×{distance} создана на ребре [{edgeIndex}], id={id}.";
}
[McpServerTool(Name = "shell")]
[Description("Превратить тело активной детали в оболочку (придать стенкам толщину): удалить (открыть) грани с индексами faceIndices из list_faces и задать толщину стенки thickness (мм). outward=false — толщина внутрь (габарит сохраняется), true — наружу. Возвращает id операции.")]
public async Task<string> Shell(
[Description("Индексы удаляемых (открываемых) граней из list_faces")] int[] faceIndices,
[Description("Толщина стенки, мм")] double thickness,
[Description("Толщина наружу (true) или внутрь (false)")] bool outward = false)
{
await session.ConnectAsync();
var id = await modeler.ShellAsync(faceIndices, thickness, outward);
var removed = faceIndices?.Distinct().Count() ?? 0;
return $"Оболочка толщиной {thickness} мм создана (удалено граней: {removed}), id={id}.";
}
[McpServerTool(Name = "rib")]
[Description("Построить ребро жёсткости от разомкнутого контура эскиза до тела. sketchId — эскиз с разомкнутым контуром (отрезок/ломаная) на плоскости, пересекающей тело. thickness — толщина стенки ребра (мм). side — направление достройки полотна до тела (ksRibSideEnum): left|right|up|down (если ребро не строится — перебрать значения). symmetric=true — толщина симметрична плоскости эскиза, false — в одну сторону. angle — уклон стенок (градусы). Возвращает id операции.")]
public async Task<string> Rib(int sketchId, double thickness, string side = "up", bool symmetric = true, double angle = 0)
{
await session.ConnectAsync();
var id = await modeler.RibAsync(sketchId, thickness, side, symmetric, angle);
return $"Ребро жёсткости толщиной {thickness} мм создано (side={side}), id={id}.";
}
[McpServerTool(Name = "sweep")]
[Description("Кинематическая операция: переместить замкнутый профиль (эскиз profileSketchId) вдоль траектории (эскиз pathSketchId), образуя сплошное тело. Профиль и траектория должны быть совместимы: на разных плоскостях (обычно перпендикулярных), начало траектории — на плоскости профиля; иначе операция вернёт ошибку построения. Возвращает id операции.")]
public async Task<string> Sweep(int profileSketchId, int pathSketchId)
{
await session.ConnectAsync();
var id = await modeler.SweepAsync(profileSketchId, pathSketchId);
return $"Кинематическая операция создана (профиль {profileSketchId} по траектории {pathSketchId}), id={id}.";
}
[McpServerTool(Name = "loft")]
[Description("Операция по сечениям (loft): построить сплошное тело по нескольким закрытым эскизам-сечениям (sketchIds, минимум 2, в порядке списка). Сечения обычно на параллельных плоскостях — используйте sketch_create_on_offset_plane для сечений на разной высоте. closed=true — замкнуть сечения в кольцо. Возвращает id операции.")]
public async Task<string> Loft(
[Description("Id эскизов-сечений (минимум 2, в порядке построения)")] int[] sketchIds,
[Description("Замкнуть сечения в кольцо")] bool closed = false)
{
await session.ConnectAsync();
var id = await modeler.LoftAsync(sketchIds, closed);
var sections = sketchIds?.Distinct().Count() ?? 0;
return $"Операция по сечениям создана ({sections} сечений), id={id}.";
}
[McpServerTool(Name = "linear_pattern")]
[Description("Линейный массив: размножить ранее созданные операции featureIds (их id из extrude/revolve/fillet/...) вдоль координатной оси axis (X|Y|Z). count — количество экземпляров ВСЕГО, включая исходный (>=2). step — шаг между соседними экземплярами (мм). geometric=true — быстрое геометрическое копирование (может не строиться на скруглениях/фасках кривых поверхностей). Возвращает id операции.")]
public async Task<string> LinearPattern(
[Description("Id операций-источников (из ранее созданных операций)")] int[] featureIds,
[Description("Координатная ось направления: X|Y|Z")] string axis,
[Description("Число экземпляров всего, включая исходный (>=2)")] int count,
[Description("Шаг между соседними экземплярами, мм")] double step,
[Description("Геометрическое копирование (быстрее)")] bool geometric = false)
{
await session.ConnectAsync();
var id = await modeler.LinearPatternAsync(featureIds, CoordinateAxes.Parse(axis), count, step, geometric);
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<string> 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 = "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 = "hole")]
[Description("Просверлить простое цилиндрическое отверстие (API7) на грани, выбранной по мировой точке (x,y,z в мм — точка должна лежать на грани и задаёт ЦЕНТР отверстия; сделайте model_snapshot/describe_face и прикиньте точку). diameter — диаметр (мм). throughAll=true — сквозное (depth не нужен); иначе глухое на глубину depth (мм). Направление в тело подбирается автоматически. Отличие от extrude_cut: параметрическое отверстие без эскиза.")]
public async Task<string> Hole(
double x, double y, double z,
[Description("Диаметр отверстия, мм")] double diameter,
[Description("Глубина, мм (нужна, если throughAll=false)")] double depth = 0,
[Description("Сквозное отверстие через всё тело")] bool throughAll = false)
{
await session.ConnectAsync();
await holes.HoleAsync(x, y, z, diameter, depth, throughAll);
var kind = throughAll ? "сквозное" : $"глухое на {depth} мм";
return $"Отверстие Ø{diameter} ({kind}) создано на грани в точке ({x}, {y}, {z}).";
}
[McpServerTool(Name = "draft")]
[Description("Уклон граней: наклонить грани faceIndices (из list_faces) на угол angle (градусы, строго 0 < angle < 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 < angle < 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 = "hole_counterbore")]
[Description("Отверстие с ЦЕКОВКОЙ (цилиндрическое уширение сверху — под винт с цилиндрической головкой) на грани в точке (x,y,z — центр). diameter — диаметр отверстия (мм). depth/throughAll — как у hole (для глухого depth должен быть больше spotfaceDepth). spotfaceDiameter — диаметр цековки (мм, больше diameter), spotfaceDepth — глубина цековки (мм). Направление в тело — авто.")]
public async Task<string> HoleCounterbore(
double x, double y, double z,
[Description("Диаметр отверстия, мм")] double diameter,
[Description("Диаметр цековки, мм (> diameter)")] double spotfaceDiameter,
[Description("Глубина цековки, мм")] double spotfaceDepth,
[Description("Глубина отверстия, мм (если throughAll=false)")] double depth = 0,
[Description("Сквозное отверстие")] bool throughAll = false)
{
await session.ConnectAsync();
await holes.CounterboreHoleAsync(x, y, z, diameter, depth, throughAll, spotfaceDiameter, spotfaceDepth);
var kind = throughAll ? "сквозное" : $"глубина {depth} мм";
return $"Отверстие с цековкой Ø{diameter} (цековка Ø{spotfaceDiameter}×{spotfaceDepth}, {kind}) создано в точке ({x}, {y}, {z}).";
}
[McpServerTool(Name = "hole_countersink")]
[Description("Отверстие с ЗЕНКОВКОЙ (коническое уширение сверху — под винт с потайной головкой) на грани в точке (x,y,z — центр). diameter — диаметр отверстия (мм). depth/throughAll — как у hole. sinkDiameter — диаметр зенковки (мм, больше diameter), sinkAngle — угол конуса зенковки (градусы, строго 0 < sinkAngle < 180; типично 90). Направление в тело — авто.")]
public async Task<string> HoleCountersink(
double x, double y, double z,
[Description("Диаметр отверстия, мм")] double diameter,
[Description("Диаметр зенковки, мм (> diameter)")] double sinkDiameter,
[Description("Угол конуса зенковки, градусы (0..180, типично 90)")] double sinkAngle,
[Description("Глубина отверстия, мм (если throughAll=false)")] double depth = 0,
[Description("Сквозное отверстие")] bool throughAll = false)
{
await session.ConnectAsync();
await holes.CountersinkHoleAsync(x, y, z, diameter, depth, throughAll, sinkDiameter, sinkAngle);
var kind = throughAll ? "сквозное" : $"глубина {depth} мм";
return $"Отверстие с зенковкой Ø{diameter} (зенковка Ø{sinkDiameter}, угол {sinkAngle}°, {kind}) создано в точке ({x}, {y}, {z}).";
}
[McpServerTool(Name = "hole_conic")]
[Description("Коническое (конусное) отверстие на грани в точке (x,y,z — центр). diameter — диаметр у дна (мм). conicAngle — угол конуса (градусы, строго 0 < conicAngle < 180); отверстие расширяется к поверхности. depth/throughAll — как у hole. Направление в тело — авто.")]
public async Task<string> HoleConic(
double x, double y, double z,
[Description("Диаметр у дна, мм")] double diameter,
[Description("Угол конуса, градусы (0 < conicAngle < 180)")] double conicAngle,
[Description("Глубина отверстия, мм (если throughAll=false)")] double depth = 0,
[Description("Сквозное отверстие")] bool throughAll = false)
{
await session.ConnectAsync();
await holes.ConicHoleAsync(x, y, z, diameter, depth, throughAll, conicAngle);
var kind = throughAll ? "сквозное" : $"глубина {depth} мм";
return $"Коническое отверстие Ø{diameter} (угол {conicAngle}°, {kind}) создано в точке ({x}, {y}, {z}).";
}
[McpServerTool(Name = "rebuild")]
[Description("Перестроить активный документ.")]
public async Task<string> Rebuild()
{
await session.ConnectAsync();
await modeler.RebuildAsync();
return "Документ перестроен.";
}
}