Files
kompas3d-mcp/docs/superpowers/specs/2026-05-27-drawing-linear-dimension-design.md
T
mikhail 71cafb2270 feat: drawing_add_linear_dimension — упрочнение по ревью Codex реализации
#4: отклонение нулевого NominalValue после Update (ориентация несовместима с
точками, напр. horizontal на вертикали |X2-X1|=0) — откат dim.Delete + ошибка.
#3: Parallel протестирован — работает БЕЗ явного Angle (КОМПАС авто-вычисляет угол
по точкам; (0,0)-(30,40)→50). #7: rollback-ветка покрыта тестом нулевой проекции.
#8: контракт «первый несистемный вид» — осознанный (задокументирован).

Тесты: DrawingDimensionTests 5→7 (+parallel, +нулевая проекция/откат). Итого
131 unit + 72 integration = 203.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 18:07:01 +03:00

12 KiB
Raw Blame History

Дизайн: линейный размер на виде чертежа (drawing_add_linear_dimension) через API7

Дата: 2026-05-27 Статус: реализовано и проверено (7 интеграционных + unit, всего 203); ревью Codex спека и реализации учтено

Правки по ревью реализации Codex

  • #4 нулевое значение (важно): после Update+Valid проверяем |NominalValue| > 1e-6 — ловит несовместимость ориентации с точками (horizontal для точек на одной вертикали: |X2-X1|=0), которую RequireDistinctPoints не видит. При нуле — откат (dim.Delete) + ошибка. Тест добавлен.
  • #3 Parallel (важно): добавлен интеграционный тест — параллельный (0,0)-(30,40) → 50 (3-4-5). Работает БЕЗ явного Angle (КОМПАС авто-вычисляет угол по точкам) — Parallel оставлен.
  • #7 rollback (важно): тест нулевой проекции проходит через ветку dim.Delete() (откат) — проверяет, что число размеров вида не изменилось. (Update==false/Valid==false без fault injection.)
  • #8 ассоциативность вида (минор): осознанный контракт «первый НЕсистемный вид» (задокументирован в описании инструмента и ошибке FindView).

Находка реализации: размеры НЕ в IView.ObjectCount

IView.ObjectCount считает геометрию (IDrawingContainer: линии/дуги), а размеры живут в ISymbols2DContainer отдельно → после простановки размера ObjectCount НЕ меняется (проверено: 4→4). Поэтому «размер попал в целевой вид» проверяем счётом ISymbols2DContainer.LineDimensions.Count (helper GetViewLineDimensionCountAsync), а не ObjectCount. (Гарантия непустоты у стандартных видов по ObjectCount остаётся верной — там именно геометрия.)

Правки по ревью Codex

  • Нулевая длина (#1): DrawingValidation.RequireDistinctPoints(x1,y1,x2,y2) — расстояние между точками > 1e-7 (иначе вырожденный размер); + проверка dim.Valid после Update() (откат);
    • негативный тест.
  • Контракт вида (#4): честно — «первый НЕСИСТЕМНЫЙ вид (Number != 0)» при viewNumber<=0, без претензии на ассоциативность и без QI к IAssociationView (в наших чертежах иных видов нет).
  • null-check + порядок (#9): LineDimensions.Add() проверять на null; AutoNominalValue=true выставлять явно; NominalValue читать ТОЛЬКО после Update()==true && Valid==true.
  • ViewNumbers (#7): поле required в DrawingViewsResult; инициализатор и текст MCP-ответа drawing_create_standard_views обновить (вернуть номера видов).
  • Тесты (#6): ObjectCount ЦЕЛЕВОГО вида до/после; адресация конкретного viewNumber; негатив на совпадающие точки. viewNumber не найден (#2) → понятная ошибка. 3D-конвертер точек (#8) — будущее.

Цель

Веха 2D-ЧЕРТЁЖ, инкремент 3 (размеры). После видов и основной надписи — линейный размер на ассоциативном виде: между двумя точками (в локальной СК вида) с авто-измерением длины. Размеры — самый ценный элемент оформления (несут размерную информацию). Начинаем с линейного (горизонтальный/вертикальный/параллельный); диаметральные/радиальные/угловые — далее.

Спайк: механизм подтверждён вживую (НЕ разучивать)

Спайк (DrawingDimensionSpikeTests, зелёный) подтвердил путь API7:

IView view = первый вид с Number != 0;             // системный вид — #0, пропускаем
ISymbols2DContainer symbols = (ISymbols2DContainer)view;   // COM-QI ОТ ВИДА (не от документа!)
ILineDimension dim = symbols.LineDimensions.Add();
dim.X1=x1; dim.Y1=y1; dim.X2=x2; dim.Y2=y2;        // выносные точки (ЛОКАЛЬНАЯ СК вида, мм)
dim.X3=x3; dim.Y3=y3;                               // положение размерной линии
dim.Orientation = ksLinDHorizontal;                // 0=Parallel / 1=Horizontal / 2=Vertical
bool ok = dim.Update();
double value = ((IDimensionText)dim).NominalValue; // авто-измеренное значение (мм)

Проверено (вид спереди коробки 40×30×20): размер (0,0)-(40,0) горизонтальный → Update()=True, Valid=True, NominalValue == 40 (авто |X2-X1|), view.ObjectCount вырос.

Подтверждённые факты:

  • ISymbols2DContainer берётся COM-QI ОТ КОНКРЕТНОГО IView — размер попадает именно в этот вид (не нужен Current=true). (Аналогично 3D: partIModelContainer.)
  • Координаты X1..Y3 — ЛОКАЛЬНАЯ СК вида (= координаты модели на плоскости вида, при масштабе 1:1 совпадают с мм модели). НЕ координаты листа.
  • AutoNominalValue=true по умолчаниюNominalValue = расстояние между точками (горизонтальный: |X2-X1|, вертикальный: |Y2-Y1|, параллельный: реальная длина). Значение читается ((IDimensionText)dim).NominalValue (QI).
  • Orientation (ksLineDimensionOrientationEnum): ksLinDParallel=0, ksLinDHorizontal=1, ksLinDVertical=2Kompas6Constants).
  • Update() обязателен; перестроение документа не нужно.

Адресация вида (часть инкремента)

Чтобы указать, НА КАКОМ виде ставить размер, нужен его Number. Сейчас drawing_create_standard_views возвращает только {Created, Total}. Расширяем DrawingViewsResult полем ViewNumbers (номера созданных видов) — уже собираются как newViews в CreateStandardViewsCore (тривиально: newViews.Select(v => v.Number)). Это закрывает замечание Codex из ревью видов (#10: «нет ссылок на созданные виды») и делает размеры адресуемыми.

MCP-инструмент

Инструмент Параметры Поведение
drawing_add_linear_dimension x1,y1,x2,y2 (выносные точки), x3,y3 (положение размерной линии), orientation="horizontal", viewNumber=0 Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ СК вида (мм). orientation: horizontal/vertical/parallel. viewNumber: номер вида (0 = первый ассоциативный/главный). Значение измеряется автоматически. Возвращает измеренное значение и номер вида.

Контракт: активный документ — чертёж; на чертеже есть хотя бы один ассоциативный вид. Координаты — локальные вида (см. выше). drawing_create_standard_views возвращает ViewNumbers для адресации.

Архитектура

В существующем DrawingService. Свой enum DimensionOrientation (паттерн MateType/StampField).

  • src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs — enum + static DimensionOrientations (Parse(string), ToKompasksLineDimensionOrientationEnum).
  • DrawingService.AddLinearDimensionAsync(viewNumber, x1,y1,x2,y2,x3,y3, DimensionOrientation, ct).
  • DrawingViewsResult += IReadOnlyList<int> ViewNumbers.
  • Инструмент drawing_add_linear_dimension в DrawingTools (orientation строкой → Parse).

Валидация (чистые static, unit-тест)

  • DimensionOrientations.Parse — horizontal/vertical/parallel (+рус.) → enum; иначе ArgumentException.
  • DimensionOrientations.ToKompas — enum → ksLineDimensionOrientationEnum (0/1/2).
  • DrawingValidation.RequireFiniteCoords(params) — координаты конечны (NaN/Infinity → ошибка).

Реализация

AddLinearDimensionCore: валидация координат + orientation → RequireActiveDrawing → найти вид (viewNumber<=0 → первый с Number!=0; иначе по Number, ошибка если нет) → (ISymbols2DContainer)viewLineDimensions.Add() → задать X1..Y3 + Orientation → Update() (FALSE → откат dim.Delete() + ошибка) → проверить dim.Valid (false → откат + ошибка) → вернуть AddedDimension { Value = ((IDimensionText)dim).NominalValue, ViewNumber = view.Number }. RCW точечно не освобождаем (v2-2).

Тестирование

Unit (DimensionOrientationsTests)

  • Parse — horizontal/vertical/parallel и рус.-синонимы → enum; бросает на мусоре.
  • ToKompas — каждый → 1/2/0.
  • RequireFiniteCoords — бросает на NaN/Infinity; пропускает конечные.

Integration (DrawingDimensionTests)

Паттерн: построить деталь → сохранить → создать чертёж → стандартные виды → размер → проверить → finally CloseAsync (база IntegrationTestBase).

  1. Горизонтальный (0,0)-(40,0) → Value ≈ 40; view.ObjectCount вырос.
  2. Вертикальный (0,0)-(0,20) → Value ≈ 20.
  3. Адресация вида: CreateStandardViewsAsync вернул ViewNumbers (3 шт); размер с конкретным viewNumber ставится в этот вид.
  4. Нет видов: чертёж без видов → понятная ошибка.

(Сценарий 1 = промотированный спайк; спайк-файл удаляется.)

Дальнейшее (вне спека)

  • Диаметральные/радиальные/угловые размеры; привязка к геометрии вида (ассоциативно, BaseObject); допуски/обозначения; рамка/формат листа.