675e43160e
- ConicHoleAsync (ksHTConic + IConicHoleParameters: ConicType=ksCNAngle, ConicAngle) - инструмент hole_conic (diameter у дна, conicAngle 0..180); тот же HoleCore-паттерн - интеграционный тест (конус удаляет больше цилиндра Ø10); типы отверстий завершены - 70 инструментов, 119 тестов
271 lines
23 KiB
C#
271 lines
23 KiB
C#
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 "Документ перестроен.";
|
||
}
|
||
}
|