feat(drawing): текстовые обозначения — шероховатость, текст, тех. требования
drawing_add_rough (знак шероховатости: ISymbols2DContainer.Roughs + IRoughParams, SignType ksRoughSignEnum, значение Ra/Rz через RoughParamText.Str), drawing_add_text (свободная надпись: IDrawingContainer.DrawingTexts + IText.Str), drawing_set_technical_requirements (блок тех. требований уровня документа: IDrawingDocument.TechnicalDemand.Text). - DrawingService: AddRoughAsync/AddTextAsync/SetTechnicalRequirementsAsync + ReadTechnicalRequirementsAsync + счётчики; RequireDrawingContainer/RequireTechnicalDemand - RoughSignType (enum + Parse/ToKompas), DrawingAnnotationResult (Value read-back из COM) - RequireNonEmptyText в DrawingValidation - 13 unit + 13 интеграционных тестов (всего 267 зелёных), сборка Release чистая Спайк подтвердил: значение шероховатости через IRoughParams.RoughParamText.Str (round-trip); текст НЕ в IView.ObjectCount (счёт по DrawingTexts.Count); тех. требования IsCreated False→True при первом Text.Str+Update, многострочно через \n. Ревью Codex спека учтено (height убран — это высота блока, не шрифт; guards; read-back возврат). Спек — docs/superpowers/specs/. 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.Drawings;
|
||||
|
||||
/// <summary>Результат простановки текстового обозначения (шероховатость/надпись) на виде.</summary>
|
||||
public sealed record DrawingAnnotationResult
|
||||
{
|
||||
/// <summary>Содержимое обозначения, прочитанное обратно из COM (значение Ra/Rz либо текст надписи).</summary>
|
||||
public required string Value { get; init; }
|
||||
|
||||
/// <summary>Номер вида, на котором поставлено обозначение.</summary>
|
||||
public required int ViewNumber { get; init; }
|
||||
}
|
||||
@@ -287,6 +287,148 @@ public sealed class DrawingService
|
||||
public Task<int> GetViewAngularDimensionCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.AngleDimensions?.Count ?? 0, ct);
|
||||
|
||||
// ───────────────────────── Текстовые обозначения (инкремент 6) ─────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Поставить знак шероховатости на виде в точке (<paramref name="x"/>,<paramref name="y"/>)
|
||||
/// (ЛОКАЛЬНАЯ СК вида, мм). <paramref name="value"/> — текст параметра (Ra/Rz; пусто = знак без
|
||||
/// значения). <paramref name="signType"/> — тип знака. <paramref name="angleDeg"/> — угол наклона
|
||||
/// оси знака (°). Возвращает значение (read-back) и номер вида.
|
||||
/// </summary>
|
||||
public Task<DrawingAnnotationResult> AddRoughAsync(int viewNumber, double x, double y, string? value,
|
||||
RoughSignType signType, double angleDeg, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddRoughCore(viewNumber, x, y, value, signType, angleDeg), ct);
|
||||
|
||||
private DrawingAnnotationResult AddRoughCore(int viewNumber, double x, double y, string? value,
|
||||
RoughSignType signType, double angleDeg)
|
||||
{
|
||||
DrawingValidation.RequireFiniteCoords(x, y, angleDeg);
|
||||
var text = string.IsNullOrWhiteSpace(value) ? string.Empty : value;
|
||||
|
||||
var (view, symbols) = RequireSymbols2DContainer(viewNumber);
|
||||
var roughs = symbols.Roughs
|
||||
?? throw new InvalidOperationException("Коллекция Roughs недоступна на виде.");
|
||||
var rough = roughs.Add()
|
||||
?? throw new InvalidOperationException("Roughs.Add вернул null.");
|
||||
try
|
||||
{
|
||||
rough.BranchX0 = x; rough.BranchY0 = y; rough.Angle = angleDeg;
|
||||
var rp = rough as IRoughParams
|
||||
?? throw new InvalidOperationException("Знак шероховатости не приводится к IRoughParams.");
|
||||
rp.SignType = RoughSignTypes.ToKompas(signType);
|
||||
var paramText = rp.RoughParamText
|
||||
?? throw new InvalidOperationException("RoughParamText недоступен (текст значения).");
|
||||
paramText.Str = text;
|
||||
|
||||
if (!rough.Update())
|
||||
throw new InvalidOperationException("IRough.Update вернул FALSE — знак не построен.");
|
||||
if (!rough.Valid)
|
||||
throw new InvalidOperationException("Знак шероховатости построен невалидным.");
|
||||
|
||||
var readBack = rp.RoughParamText?.Str ?? string.Empty; // read-back из COM
|
||||
return new DrawingAnnotationResult { Value = readBack, ViewNumber = view.Number };
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { rough.Delete(); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Число знаков шероховатости на виде (для проверки попадания в вид).</summary>
|
||||
public Task<int> GetViewRoughCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireSymbols2DContainer(viewNumber).Symbols.Roughs?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Поставить свободную текстовую надпись на виде в точке (<paramref name="x"/>,<paramref name="y"/>)
|
||||
/// (ЛОКАЛЬНАЯ СК вида, мм) стилем по умолчанию. <paramref name="text"/> — содержимое (\n — многострочно).
|
||||
/// <paramref name="angleDeg"/> — угол наклона текста (°). Возвращает текст (read-back) и номер вида.
|
||||
/// </summary>
|
||||
public Task<DrawingAnnotationResult> AddTextAsync(int viewNumber, double x, double y, string text,
|
||||
double angleDeg, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => AddTextCore(viewNumber, x, y, text, angleDeg), ct);
|
||||
|
||||
private DrawingAnnotationResult AddTextCore(int viewNumber, double x, double y, string text, double angleDeg)
|
||||
{
|
||||
DrawingValidation.RequireNonEmptyText(text, nameof(text));
|
||||
DrawingValidation.RequireFiniteCoords(x, y, angleDeg);
|
||||
|
||||
var (view, container) = RequireDrawingContainer(viewNumber);
|
||||
var texts = container.DrawingTexts
|
||||
?? throw new InvalidOperationException("Коллекция DrawingTexts недоступна на виде.");
|
||||
var dt = texts.Add()
|
||||
?? throw new InvalidOperationException("DrawingTexts.Add вернул null.");
|
||||
try
|
||||
{
|
||||
dt.X = x; dt.Y = y; dt.Angle = angleDeg;
|
||||
var content = dt as IText
|
||||
?? throw new InvalidOperationException("Надпись не приводится к IText.");
|
||||
content.Str = text;
|
||||
|
||||
if (!dt.Update())
|
||||
throw new InvalidOperationException("IDrawingText.Update вернул FALSE — надпись не построена.");
|
||||
if (!dt.Valid)
|
||||
throw new InvalidOperationException("Надпись построена невалидной.");
|
||||
|
||||
var readBack = ((IText)dt).Str ?? string.Empty; // read-back из COM
|
||||
return new DrawingAnnotationResult { Value = readBack, ViewNumber = view.Number };
|
||||
}
|
||||
catch
|
||||
{
|
||||
try { dt.Delete(); } catch { /* лучшее усилие */ }
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Число текстовых надписей на виде (для проверки попадания в вид; текст в
|
||||
/// IDrawingContainer, а не в IView.ObjectCount).</summary>
|
||||
public Task<int> GetViewTextCountAsync(int viewNumber, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireDrawingContainer(viewNumber).Container.DrawingTexts?.Count ?? 0, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Задать технические требования активного чертежа (единый блок над основной надписью; перезаписывает
|
||||
/// прежние). <paramref name="text"/> — строки через \n. Возвращает число строк.
|
||||
/// </summary>
|
||||
public Task<int> SetTechnicalRequirementsAsync(string text, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => SetTechnicalRequirementsCore(text), ct);
|
||||
|
||||
private int SetTechnicalRequirementsCore(string text)
|
||||
{
|
||||
DrawingValidation.RequireNonEmptyText(text, nameof(text));
|
||||
var normalized = text.Replace("\r\n", "\n");
|
||||
|
||||
var td = RequireTechnicalDemand();
|
||||
var content = td.Text
|
||||
?? throw new InvalidOperationException("Текст технических требований недоступен (Text == null).");
|
||||
content.Str = normalized; // Str замещает содержимое (как у штампа)
|
||||
if (!td.Update())
|
||||
throw new InvalidOperationException("ITechnicalDemand.Update вернул FALSE — требования не заданы.");
|
||||
|
||||
// Число содержательных строк: хвостовые пустые строки отбрасываем ("a\n" → 1).
|
||||
return normalized.TrimEnd('\n').Split('\n').Length;
|
||||
}
|
||||
|
||||
/// <summary>Прочитать текст технических требований активного чертежа (для проверки/диагностики).</summary>
|
||||
public Task<string> ReadTechnicalRequirementsAsync(CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() => RequireTechnicalDemand().Text?.Str ?? string.Empty, ct);
|
||||
|
||||
private ITechnicalDemand RequireTechnicalDemand()
|
||||
{
|
||||
var doc = RequireActiveDrawing() as IDrawingDocument
|
||||
?? throw new InvalidOperationException("Активный чертёж не приводится к IDrawingDocument.");
|
||||
return doc.TechnicalDemand
|
||||
?? throw new InvalidOperationException("У чертежа недоступны технические требования (TechnicalDemand == null).");
|
||||
}
|
||||
|
||||
/// <summary>Вид + его IDrawingContainer (QI от вида). Для текстовых надписей (DrawingTexts).</summary>
|
||||
private (IView View, IDrawingContainer Container) RequireDrawingContainer(int viewNumber)
|
||||
{
|
||||
var view = FindView(viewNumber);
|
||||
var container = view as IDrawingContainer
|
||||
?? throw new InvalidOperationException("Вид не приводится к IDrawingContainer.");
|
||||
return (view, container);
|
||||
}
|
||||
|
||||
/// <summary>Вид + его ISymbols2DContainer (QI от вида). Общий для линейных/диаметральных/… размеров.</summary>
|
||||
private (IView View, ISymbols2DContainer Symbols) RequireSymbols2DContainer(int viewNumber)
|
||||
{
|
||||
|
||||
@@ -48,6 +48,13 @@ public static class DrawingValidation
|
||||
throw new ArgumentOutOfRangeException(nameof(radius), "Радиус должен быть конечным и > 0.");
|
||||
}
|
||||
|
||||
/// <summary>Проверить, что текст не пустой и не только из пробелов.</summary>
|
||||
public static void RequireNonEmptyText(string? text, string paramName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
throw new ArgumentException("Текст не должен быть пустым.", paramName);
|
||||
}
|
||||
|
||||
/// <summary>Минимальное расстояние между выносными точками линейного размера (мм).</summary>
|
||||
private const double MinPointDistance = 1e-7;
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
using Kompas6Constants;
|
||||
|
||||
namespace Kompas.Mcp.Core.Drawings;
|
||||
|
||||
/// <summary>Тип знака шероховатости (обёртка над <see cref="ksRoughSignEnum"/>).</summary>
|
||||
public enum RoughSignType
|
||||
{
|
||||
/// <summary>Без указания вида обработки.</summary>
|
||||
NoProcessing,
|
||||
/// <summary>С удалением слоя материала.</summary>
|
||||
DeleteMaterial,
|
||||
/// <summary>Без удаления слоя материала.</summary>
|
||||
WithoutDeleteMaterial,
|
||||
}
|
||||
|
||||
/// <summary>Маппинг типа знака шероховатости ↔ перечисление КОМПАС и разбор строки.</summary>
|
||||
public static class RoughSignTypes
|
||||
{
|
||||
public static ksRoughSignEnum ToKompas(RoughSignType type) => type switch
|
||||
{
|
||||
RoughSignType.NoProcessing => ksRoughSignEnum.ksNoProcessingType,
|
||||
RoughSignType.DeleteMaterial => ksRoughSignEnum.ksDeleteMaterial,
|
||||
RoughSignType.WithoutDeleteMaterial => ksRoughSignEnum.ksWithoutDeleteMaterial,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Неизвестный тип знака шероховатости."),
|
||||
};
|
||||
|
||||
/// <summary>Разобрать строку типа знака (англ./рус.) в <see cref="RoughSignType"/>.</summary>
|
||||
public static RoughSignType Parse(string value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
return value.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"delete" or "deletematerial" or "с удалением" or "с удалением материала" => RoughSignType.DeleteMaterial,
|
||||
"without" or "withoutdelete" or "без удаления" or "без удаления материала" => RoughSignType.WithoutDeleteMaterial,
|
||||
"none" or "noprocessing" or "без указания" or "любая" => RoughSignType.NoProcessing,
|
||||
_ => throw new ArgumentException(
|
||||
$"Неизвестный тип знака шероховатости '{value}'. Допустимо: delete, without, none.",
|
||||
nameof(value)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -127,4 +127,54 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing)
|
||||
var r = await drawing.AddAngularDimensionAsync(viewNumber, xc, yc, x1, y1, x2, y2, x3, y3, t);
|
||||
return $"Угловой размер поставлен на виде {r.ViewNumber}, {r.Value:0.###}°.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_add_rough")]
|
||||
[Description("Поставить знак шероховатости на виде активного чертежа в точке (x,y) в ЛОКАЛЬНОЙ СК вида " +
|
||||
"(мм). value — текст параметра (например \"Ra 1.6\" или \"Rz 40\"; пусто = знак без значения). " +
|
||||
"signType: delete (с удалением слоя материала) | without (без удаления) | none (без указания вида " +
|
||||
"обработки). angle — угол наклона оси знака в градусах. viewNumber — номер вида из " +
|
||||
"drawing_create_standard_views (0 = первый/главный). Возвращает значение и номер вида.")]
|
||||
public async Task<string> AddRough(
|
||||
[Description("X положения знака (СК вида, мм)")] double x,
|
||||
[Description("Y положения знака")] double y,
|
||||
[Description("Текст параметра, напр. \"Ra 1.6\" (пусто = без значения)")] string value = "",
|
||||
[Description("Тип знака: delete | without | none")] string signType = "delete",
|
||||
[Description("Угол наклона оси знака, градусы")] double angle = 0,
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var st = RoughSignTypes.Parse(signType);
|
||||
var r = await drawing.AddRoughAsync(viewNumber, x, y, value, st, angle);
|
||||
var shown = string.IsNullOrEmpty(r.Value) ? "(без значения)" : r.Value;
|
||||
return $"Знак шероховатости поставлен на виде {r.ViewNumber}: {shown}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_add_text")]
|
||||
[Description("Поставить свободную текстовую надпись на виде активного чертежа в точке (x,y) в ЛОКАЛЬНОЙ " +
|
||||
"СК вида (мм) стилем по умолчанию. text — содержимое надписи (\\n — несколько строк). angle — угол " +
|
||||
"наклона текста в градусах. viewNumber — номер вида из drawing_create_standard_views (0 = первый/" +
|
||||
"главный). Возвращает поставленный текст и номер вида.")]
|
||||
public async Task<string> AddText(
|
||||
[Description("X точки привязки (СК вида, мм)")] double x,
|
||||
[Description("Y точки привязки")] double y,
|
||||
[Description("Текст надписи (\\n — многострочно)")] string text = "",
|
||||
[Description("Угол наклона текста, градусы")] double angle = 0,
|
||||
[Description("Номер вида (0 = первый/главный)")] int viewNumber = 0)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var r = await drawing.AddTextAsync(viewNumber, x, y, text, angle);
|
||||
return $"Надпись поставлена на виде {r.ViewNumber}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "drawing_set_technical_requirements")]
|
||||
[Description("Задать технические требования активного чертежа — единый текстовый блок над основной " +
|
||||
"надписью (ГОСТ). text — содержимое (\\n разделяет пункты/строки, например \"1. ...\\n2. ...\"). " +
|
||||
"Перезаписывает прежние технические требования (не добавляет). Возвращает число строк.")]
|
||||
public async Task<string> SetTechnicalRequirements(
|
||||
[Description("Текст требований (\\n — строки)")] string text)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var lines = await drawing.SetTechnicalRequirementsAsync(text);
|
||||
return $"Технические требования заданы ({lines} стр.).";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user