bc52b5d920
- VariableService: CreateVariableAsync/SetVariableAsync/DeleteVariableAsync/GetVariableValueAsync - создание ТОЛЬКО на ksFeature.VariableCollection (ksPart — только внешние); RebuildModel + re-fetch - Expression — ведущее поле (константа/формула со ссылками); value пересчитывается - list_variables (ModelInspectionService) исправлен: читает feature-коллекцию (все переменные) - инструменты create_variable/set_variable/delete_variable (VariableTools) + DI - интеграционный тест CRUD round-trip (создание, формула width*2, удаление по зависимостям) - ОГРАНИЧЕНИЕ: переменные драйвят геометрию лишь в параметрической модели (эскизы на литералах — нет) - 116 тестов; спек docs/superpowers/specs/2026-05-27-parametrics-design.md
112 lines
6.4 KiB
C#
112 lines
6.4 KiB
C#
using System.Globalization;
|
|
using System.Runtime.Versioning;
|
|
using Kompas.Mcp.Core.Threading;
|
|
using Kompas6API5;
|
|
using Kompas6Constants3D;
|
|
|
|
namespace Kompas.Mcp.Core.Modeling;
|
|
|
|
/// <summary>
|
|
/// Параметрика: создание и изменение переменных модели (API5 <c>ksVariable</c>).
|
|
/// Создание возможно только на коллекции переменных корневого <c>ksFeature</c> (не <c>ksPart</c>).
|
|
/// <para>ВАЖНО: переменная управляет геометрией лишь в параметрической модели (где размеры
|
|
/// эскизов связаны с именованными переменными). Эскизы, построенные литеральными координатами,
|
|
/// не имеют именованных размеров — для них переменные хранятся, но геометрию не меняют.</para>
|
|
/// </summary>
|
|
[SupportedOSPlatform("windows")]
|
|
public sealed class VariableService
|
|
{
|
|
private readonly KompasSession _session;
|
|
private readonly KompasDispatcher _dispatcher;
|
|
|
|
public VariableService(KompasSession session, KompasDispatcher dispatcher)
|
|
{
|
|
_session = session ?? throw new ArgumentNullException(nameof(session));
|
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
|
}
|
|
|
|
/// <summary>Создать новую переменную модели с начальным значением. external=true — внешняя
|
|
/// (доступна для управления из сборки). Возвращает вычисленное значение.</summary>
|
|
public Task<double> CreateVariableAsync(string name, double value, string? note = null, bool external = false, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
RequireName(name);
|
|
if (!double.IsFinite(value)) throw new ArgumentException("Значение должно быть конечным.", nameof(value));
|
|
var (part, vc) = RootVariables();
|
|
if (vc.GetByName(name, true, false) is ksVariable)
|
|
throw new InvalidOperationException($"Переменная '{name}' уже существует.");
|
|
var v = vc.AddNewVariable(name, value, note ?? "") as ksVariable
|
|
?? throw new InvalidOperationException("AddNewVariable вернул null (недопустимое имя?).");
|
|
if (external) v.external = true;
|
|
part.RebuildModel();
|
|
return ReadValueFresh(name); // значение перечитываем после перестроения
|
|
}, ct);
|
|
|
|
/// <summary>Задать выражение переменной (константа «30» или формула «len*2+5») и перестроить.
|
|
/// Возвращает вычисленное значение.</summary>
|
|
public Task<double> SetVariableAsync(string name, string expression, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
RequireName(name);
|
|
if (string.IsNullOrWhiteSpace(expression))
|
|
throw new ArgumentException("Выражение не может быть пустым.", nameof(expression));
|
|
var (part, vc) = RootVariables();
|
|
var v = RequireVariable(vc, name);
|
|
v.Expression = expression;
|
|
part.RebuildModel();
|
|
return ReadValueFresh(name); // переменную перечитываем после перестроения (value пересчитан)
|
|
}, ct);
|
|
|
|
/// <summary>Удалить переменную модели.</summary>
|
|
public Task DeleteVariableAsync(string name, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
RequireName(name);
|
|
var (part, vc) = RootVariables();
|
|
RequireVariable(vc, name); // понятная ошибка, если её нет
|
|
if (!vc.RemoveVariable(name))
|
|
throw new InvalidOperationException($"Не удалось удалить переменную '{name}'.");
|
|
part.RebuildModel();
|
|
}, ct);
|
|
|
|
/// <summary>Прочитать текущее (вычисленное) значение переменной.</summary>
|
|
public Task<double> GetVariableValueAsync(string name, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
RequireName(name);
|
|
var (_, vc) = RootVariables();
|
|
return RequireVariable(vc, name).value;
|
|
}, ct);
|
|
|
|
/// <summary>Перечитать значение переменной из свежей коллекции (после RebuildModel оно пересчитано).</summary>
|
|
private double ReadValueFresh(string name)
|
|
{
|
|
var (_, vc) = RootVariables();
|
|
return RequireVariable(vc, name).value;
|
|
}
|
|
|
|
private static void RequireName(string name)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
throw new ArgumentException("Имя переменной не может быть пустым.", nameof(name));
|
|
}
|
|
|
|
private static ksVariable RequireVariable(ksVariableCollection vc, string name)
|
|
=> vc.GetByName(name, true, false) as ksVariable
|
|
?? throw new KeyNotFoundException($"Переменная '{name}' не найдена.");
|
|
|
|
/// <summary>Корневой ksFeature детали и его коллекция переменных (на ней работает AddNewVariable).</summary>
|
|
private (ksPart part, ksVariableCollection vc) RootVariables()
|
|
{
|
|
var doc3d = _session.Kompas.ActiveDocument3D() as ksDocument3D
|
|
?? throw new InvalidOperationException("Нет активного 3D-документа.");
|
|
var part = doc3d.GetPart((short)Part_Type.pTop_Part) as ksPart
|
|
?? throw new InvalidOperationException("Не удалось получить вершинный компонент (ksPart).");
|
|
var feature = part.GetFeature() as ksFeature
|
|
?? throw new InvalidOperationException("GetFeature() вернул null (нет дерева построения).");
|
|
var vc = feature.VariableCollection as ksVariableCollection
|
|
?? throw new InvalidOperationException("VariableCollection вернул не ksVariableCollection.");
|
|
return (part, vc);
|
|
}
|
|
}
|