feat: assembly_add_mate — сопряжения компонентов сборки (API7)
Веха СБОРКИ, инкремент 2. AssemblyService.AddMateAsync (API7): top.MateConstraints
→ Add(MateConstraintType) → BaseObject1/2 (грани через FindObjectsByPoint
FirstLevel=false) → ParamValue → Update → RebuildModel. Свой enum MateType
(Coincidence/Distance) → MateConstraintType. MCP-инструмент assembly_add_mate.
Подтверждено спайком и тестами: coincidence сводит грани заподлицо (ширина 120→40),
distance(30) держит зазор 30 (→70); Update=True Valid=True. Решатель может двигать
любой компонент → проверка по ширине габарита.
Ревью Codex учтено: scope урезан до coincidence/distance (только геометрически
проверенные); валидация value по типу (RequireMateValue); полный откат при любой
ошибке после Add (mate.Owner.Delete); возврат AddedMate{Type,Valid}; усиленные
ассерты (Valid, размеры компонентов неизменны). Точечный release RCW отклонён
(конвенция v2-2). Прочие типы (parallel/concentric/angle/…) — будущие инкременты.
Тесты: +14 unit (Mates.Parse/RequiresValue, RequireMateValue), +2 integration
(AssemblyMateTests). Итого 97 unit + 57 integration = 154 зелёных.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
namespace Kompas.Mcp.Core.Assemblies;
|
||||
|
||||
/// <summary>Результат наложения сопряжения между компонентами сборки.</summary>
|
||||
public sealed record AddedMate
|
||||
{
|
||||
/// <summary>Тип наложенного сопряжения (как был запрошен).</summary>
|
||||
public required MateType Type { get; init; }
|
||||
|
||||
/// <summary>true — сопряжение успешно построено и не вырождено после перестроения.</summary>
|
||||
public required bool Valid { get; init; }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Runtime.Versioning;
|
||||
using Kompas.Mcp.Core.Threading;
|
||||
using Kompas6Constants;
|
||||
using Kompas6Constants3D;
|
||||
using KompasAPI7;
|
||||
|
||||
namespace Kompas.Mcp.Core.Assemblies;
|
||||
@@ -79,6 +80,77 @@ public sealed class AssemblyService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Наложить сопряжение <paramref name="type"/> между гранями двух компонентов активной сборки,
|
||||
/// выбранными по мировым точкам (<paramref name="x1"/>,<paramref name="y1"/>,<paramref name="z1"/>)
|
||||
/// и (<paramref name="x2"/>,<paramref name="y2"/>,<paramref name="z2"/>). Для расстояния
|
||||
/// <paramref name="value"/> — зазор (мм). Решатель спозиционирует компоненты; результат — статус
|
||||
/// построенного сопряжения.
|
||||
/// </summary>
|
||||
public Task<AddedMate> AddMateAsync(MateType type, double x1, double y1, double z1,
|
||||
double x2, double y2, double z2, double value, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddMateCore(type, x1, y1, z1, x2, y2, z2, value), ct);
|
||||
|
||||
private AddedMate AddMateCore(MateType type, double x1, double y1, double z1,
|
||||
double x2, double y2, double z2, double value)
|
||||
{
|
||||
AssemblyValidation.RequireFinitePosition(x1, y1, z1);
|
||||
AssemblyValidation.RequireFinitePosition(x2, y2, z2);
|
||||
AssemblyValidation.RequireMateValue(Mates.RequiresValue(type), value);
|
||||
|
||||
var top = RequireActiveAssembly();
|
||||
var face1 = FindFaceInAssembly(top, x1, y1, z1)
|
||||
?? throw new InvalidOperationException(
|
||||
$"В точке ({x1},{y1},{z1}) не найдена грань компонента. Укажите точку в середине грани (model_snapshot).");
|
||||
var face2 = FindFaceInAssembly(top, x2, y2, z2)
|
||||
?? throw new InvalidOperationException(
|
||||
$"В точке ({x2},{y2},{z2}) не найдена грань компонента. Укажите точку в середине грани (model_snapshot).");
|
||||
|
||||
var mates = top.MateConstraints
|
||||
?? throw new InvalidOperationException("Не удалось получить коллекцию сопряжений сборки (MateConstraints).");
|
||||
var mate = mates.Add(Mates.ToConstraintType(type))
|
||||
?? throw new InvalidOperationException("MateConstraints.Add вернул null.");
|
||||
|
||||
try
|
||||
{
|
||||
mate.BaseObject1 = (IModelObject)face1;
|
||||
mate.BaseObject2 = (IModelObject)face2;
|
||||
if (Mates.RequiresValue(type))
|
||||
mate.ParamValue = value;
|
||||
|
||||
if (!mate.Update())
|
||||
throw new InvalidOperationException(
|
||||
"Update сопряжения вернул FALSE — связь не наложена (несовместимые грани/значение?).");
|
||||
if (!top.RebuildModel(true))
|
||||
throw new InvalidOperationException("RebuildModel вернул FALSE — сборка не перестроена после сопряжения.");
|
||||
|
||||
return new AddedMate { Type = type, Valid = mate.Valid };
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Полный откат: при ЛЮБОЙ ошибке после Add удаляем сопряжение, чтобы не оставить «сироту».
|
||||
try { mate.Owner?.Delete(); top.RebuildModel(true); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static IFace? FindFaceInAssembly(IPart7 top, double x, double y, double z)
|
||||
{
|
||||
// FirstLevel=false — спускаемся в компоненты, чтобы получить их грани в контексте сборки.
|
||||
var hit = top.FindObjectsByPoint(x, y, z, false);
|
||||
switch (hit)
|
||||
{
|
||||
case IFace f: return f;
|
||||
case object[] arr:
|
||||
foreach (var o in arr) if (o is IFace face) return face;
|
||||
return null;
|
||||
case System.Collections.IEnumerable en:
|
||||
foreach (var o in en) if (o is IFace face) return face;
|
||||
return null;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Откатить вставку: удалить компоненты, появившиеся сверх <paramref name="beforeCount"/> (обычно
|
||||
/// один — только что вставленный). Best-effort, но с верификацией: компонент удаляется через
|
||||
|
||||
@@ -29,10 +29,22 @@ public static class AssemblyValidation
|
||||
return Path.GetFullPath(trimmed);
|
||||
}
|
||||
|
||||
/// <summary>Проверить, что координаты вставки конечны (отсекает NaN/Infinity).</summary>
|
||||
/// <summary>Проверить, что координаты конечны (отсекает NaN/Infinity).</summary>
|
||||
public static void RequireFinitePosition(double x, double y, double z)
|
||||
{
|
||||
if (!double.IsFinite(x) || !double.IsFinite(y) || !double.IsFinite(z))
|
||||
throw new ArgumentException("Координаты вставки компонента должны быть конечными числами (не NaN/Infinity).");
|
||||
throw new ArgumentException("Координаты должны быть конечными числами (не NaN/Infinity).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверить числовое значение сопряжения. Если значение требуется (<paramref name="requiresValue"/>,
|
||||
/// напр. расстояние) — оно должно быть конечным и неотрицательным.
|
||||
/// </summary>
|
||||
public static void RequireMateValue(bool requiresValue, double value)
|
||||
{
|
||||
if (!requiresValue) return; // для типов без параметра значение игнорируется
|
||||
if (!double.IsFinite(value) || value < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value),
|
||||
"Значение сопряжения (расстояние) должно быть конечным и неотрицательным.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Kompas6Constants3D;
|
||||
|
||||
namespace Kompas.Mcp.Core.Assemblies;
|
||||
|
||||
/// <summary>
|
||||
/// Дружелюбный тип сопряжения (обёртка над <see cref="MateConstraintType"/>). Пока поддержаны только
|
||||
/// геометрически проверенные типы — совпадение и расстояние; остальные (параллельность, соосность,
|
||||
/// угол и т.д.) добавятся в следующих инкрементах вместе с тестами.
|
||||
/// </summary>
|
||||
public enum MateType
|
||||
{
|
||||
/// <summary>Совпадение (грани лежат на одной плоскости, заподлицо).</summary>
|
||||
Coincidence,
|
||||
/// <summary>На расстоянии (постоянный зазор между гранями, мм).</summary>
|
||||
Distance,
|
||||
}
|
||||
|
||||
/// <summary>Маппинг дружелюбного типа сопряжения ↔ перечисление API КОМПАС и разбор строки.</summary>
|
||||
public static class Mates
|
||||
{
|
||||
/// <summary>Сопряжения, которым нужно числовое значение (<c>ParamValue</c>): расстояние.</summary>
|
||||
public static bool RequiresValue(MateType type) => type is MateType.Distance;
|
||||
|
||||
public static MateConstraintType ToConstraintType(MateType type) => type switch
|
||||
{
|
||||
MateType.Coincidence => MateConstraintType.mc_Coincidence,
|
||||
MateType.Distance => MateConstraintType.mc_Distance,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Неизвестный тип сопряжения."),
|
||||
};
|
||||
|
||||
/// <summary>Разобрать строку типа сопряжения (англ./рус.) в <see cref="MateType"/>.</summary>
|
||||
public static MateType Parse(string value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"coincidence" or "coincident" or "совпадение" => MateType.Coincidence,
|
||||
"distance" or "расстояние" => MateType.Distance,
|
||||
_ => throw new ArgumentException(
|
||||
$"Неизвестный/неподдерживаемый тип сопряжения '{value}'. Сейчас поддержаны: " +
|
||||
"coincidence (совпадение), distance (расстояние).", nameof(value)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,26 @@ public sealed class AssemblyTools(KompasSession session, AssemblyService assembl
|
||||
var c = await assembly.AddComponentAsync(filePath, x, y, z);
|
||||
return $"Компонент '{c.Name}' вставлен в сборку, индекс {c.Index}, позиция ({x}, {y}, {z}).";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "assembly_add_mate")]
|
||||
[Description("Наложить сопряжение между гранями ДВУХ компонентов активной сборки, выбранными по " +
|
||||
"мировым точкам (x1,y1,z1) и (x2,y2,z2) — точки в СЕРЕДИНЕ граней (мм). mateType: " +
|
||||
"'coincidence' (совпадение, грани заподлицо) или 'distance' (на расстоянии value мм между гранями). " +
|
||||
"Решатель спозиционирует компоненты. Сделайте model_snapshot/describe_face, прикиньте точки на гранях.")]
|
||||
public async Task<string> AddMate(
|
||||
[Description("Тип сопряжения: coincidence | distance")] string mateType,
|
||||
[Description("X точки на грани компонента 1, мм")] double x1,
|
||||
[Description("Y точки на грани компонента 1, мм")] double y1,
|
||||
[Description("Z точки на грани компонента 1, мм")] double z1,
|
||||
[Description("X точки на грани компонента 2, мм")] double x2,
|
||||
[Description("Y точки на грани компонента 2, мм")] double y2,
|
||||
[Description("Z точки на грани компонента 2, мм")] double z2,
|
||||
[Description("Зазор для distance, мм (для coincidence не нужен)")] double value = 0)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var type = Mates.Parse(mateType);
|
||||
var m = await assembly.AddMateAsync(type, x1, y1, z1, x2, y2, z2, value);
|
||||
var valueText = Mates.RequiresValue(type) ? $", значение {value} мм" : "";
|
||||
return $"Сопряжение '{type}'{valueText} наложено (valid={m.Valid}).";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user