using System.ComponentModel;
using Kompas.Mcp.Core;
using Kompas.Mcp.Core.Modeling;
using ModelContextProtocol.Server;
namespace Kompas.Mcp.Host.Tools;
/// Инструменты формообразующих операций.
[McpServerToolType]
public sealed class FeatureTools(KompasSession session, PartModeler modeler, HoleService holes)
{
[McpServerTool(Name = "extrude_boss")]
[Description("Выдавить эскиз, добавив материал (на глубину depth, мм). Возвращает id операции.")]
public async Task 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Rebuild()
{
await session.ConnectAsync();
await modeler.RebuildAsync();
return "Документ перестроен.";
}
}