Удалены устаревшие документы суперспособностей (specs/plans) и вспомогательные доки проекта
ci / build (push) Successful in 37s

This commit is contained in:
2026-07-31 09:02:45 +03:00
parent f904daaad2
commit b022a91dd9
40 changed files with 0 additions and 8980 deletions
-271
View File
@@ -1,271 +0,0 @@
# Каталог внешнего CAD-контракта для автономного агента
## Назначение и критерий полноты
Этот документ разделяет два слоя: **текущий контракт** из 84 публично зарегистрированных инструментов Host и **целевые расширения**, необходимые автономному агенту. Для существующего метода статус оценивает только поведение, прямо заявленное в его строке: отсутствие соседних `list/update/delete` или явного выбора документа не понижает полностью выполненный метод до частичного. Такие системные разрывы вынесены в отдельные целевые строки.
Контракт считается полным не по числу методов, а когда агент может замкнуть цикл: обнаружить состояние, выбрать адресуемые объекты, изменить модель, проверить результат и сохранить его либо безопасно откатить. Для мутирующих семейств проверяется симметрия `list/describe → create → update/transform → delete → validate`.
Общее текущее ограничение: все документные, модельные, сборочные и чертёжные операции неявно работают только с активным документом. Стабильного идентификатора документа и явного параметра выбора документа нет. Поэтому одновременная надёжная работа с несколькими открытыми документами пока невозможна.
## Легенда
| Поле | Значения |
|---|---|
| Статус | ✅ реализован · 🟡 частично · ⬜ не реализован · ⛔ ограничен платформой |
| Необходимость | `Core` — нужен для замкнутого базового цикла · `Advanced` — нужен для типового профессионального сценария · `Optional` — расширяет охват или удобство |
| Приоритет | `P0` — замыкает автономный цикл · `P1` — снимает существенное профессиональное ограничение · `P2` — расширяет охват; `—` — пробела нет |
`🟡 частично` означает, что имя уже существует, но его результат, адресуемость или набор вариантов недостаточны для заявленного целевого поведения. `⛔ ограничен платформой` означает, что надёжное внешнее поведение не подтверждено и требует отдельного технического исследования либо другого режима исполнения.
## Покрытие сквозных сценариев
| Сценарий | Обнаружить состояние | Изменить | Проверить | Сохранить | Восстановить | Итог и блокирующие пробелы |
|---|---|---|---|---|---|---|
| Создать и сохранить параметрическую 3D-деталь | частично: структура и переменные читаются | частично: операции строятся, но размеры созданных эскизов не связаны с переменными | частично: геометрия и ошибки проверяются, но созданные сущности не имеют полного адресного цикла | частично: только активный документ | неполный: нет управляемого checkpoint lifecycle и отката | **неполный** — нет надёжной параметризации эскиза, изменения/удаления операций и восстановления |
| Открыть или импортировать модель, локально изменить и проверить | частично: тела, грани и рёбра видны по индексам, но из них нельзя получить selector с ревизией | частично: доступны перемещение грани, рассечение, перенос тела и объединение | частично: есть структурная и числовая проверка | частично: только активный документ | неполный | **частичный** — нет полного selector lifecycle, удаления тел, subtract/intersect и безопасного отката |
| Создать сборку, разместить компоненты, наложить сопряжения и проверить структуру | неполный: только верхний уровень, без списка сопряжений | частично: вставка и два типа сопряжений | неполный: нет структурной диагностики решённой сборки; проверка коллизий остаётся Advanced-пробелом | частично: только активный документ | неполный | **неполный** — нет рекурсивной структуры, placement, удаления, полного семейства сопряжений и структурной валидации |
| Создать основные виды чертежа, оформить и сохранить | неполный: нет агрегированного паспорта, инвентаризации листов, видов и аннотаций | частично: основные виды и базовые обозначения создаются | неполный: нет машинной валидации видов, ассоциаций и оформления | частично: только активный документ | неполный | **неполный** — нет describe/validate, CRUD видов и аннотаций, разрезов, баз, допусков, осевых и многолистности |
| Выполнить геометрическую и документную инспекцию без визуального анализа | частично: модель описывается структурно, но нет списка документов и вершин | не требуется | частично: измерение принимает вершины, которые нельзя предварительно перечислить; индексы нельзя преобразовать в устойчивый selector с ревизией | не требуется | не требуется | **частичный** — отсутствуют multi-document discovery, вершины и полный selector lifecycle |
## Методы внешнего контракта
### Сессия и документы
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `kompas_connect` | Подключить CAD-сессию и вернуть сведения о среде | ✅ реализован | Core | — | — |
| `kompas_status` | Получить состояние подключения | ✅ реализован | Core | — | — |
| `kompas_set_visible` | Показать или скрыть пользовательское окно | ✅ реализован | Optional | — | — |
| `document_create` | Создать документ заданного типа и сделать его активным | ✅ реализован | Core | — | — |
| `document_open` | Открыть документ с диска и сделать его активным | ✅ реализован | Core | — | — |
| `document_save_as` | Сохранить активный документ по новому пути | ✅ реализован | Core | — | — |
| `document_save` | Сохранить активный документ | ✅ реализован | Core | — | — |
| `document_close` | Закрыть активный документ с выбранным режимом сохранения | ✅ реализован | Core | — | — |
| `document_active` | Описать активный документ | ✅ реализован | Core | — | — |
| `list_documents` | Перечислить открытые документы с типом, путём, состоянием изменений и `document_id` | ⬜ не реализован | Core | P0 | Требуется основа multi-document контура |
| `document_activate` | Сделать документ активным по `document_id` | ⬜ не реализован | Core | P0 | Без него невозможно надёжно переключать контекст |
| `document_describe` | Прочитать свойства и состояние указанного документа без его активации | ⬜ не реализован | Core | P0 | Нужна документная инспекция вне глобального active state |
| `document_set_properties` | Изменить стандартные и пользовательские свойства документа | ⬜ не реализован | Advanced | P1 | Нет внешнего управления метаданными |
### 2D-геометрия и эскизы
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `sketch_create` | Создать эскиз на базовой плоскости и вернуть сессионный идентификатор | ✅ реализован | Core | — | — |
| `sketch_create_on_face` | Создать эскиз на плоской грани, выбранной точкой | ✅ реализован | Advanced | — | — |
| `sketch_create_on_offset_plane` | Создать эскиз на смещённой плоскости | ✅ реализован | Advanced | — | — |
| `sketch_add_circle` | Добавить окружность в открытый эскиз | ✅ реализован | Core | — | — |
| `sketch_add_line` | Добавить отрезок в открытый эскиз | ✅ реализован | Core | — | — |
| `sketch_create_on_face_index` | Создать эскиз на грани по текущему индексу | ✅ реализован | Core | — | — |
| `sketch_add_axis` | Добавить осевую линию в открытый эскиз | ✅ реализован | Core | — | — |
| `sketch_add_rectangle` | Добавить прямоугольник в открытый эскиз | ✅ реализован | Core | — | — |
| `sketch_close` | Завершить редактирование текущего открытого эскиза | ✅ реализован | Core | — | — |
| `sketch_add_arc_3points` | Добавить дугу по трём точкам | ✅ реализован | Advanced | — | — |
| `sketch_add_arc` | Добавить дугу по центру, радиусу и углам | ✅ реализован | Advanced | — | — |
| `sketch_add_ellipse` | Добавить эллипс | ✅ реализован | Advanced | — | — |
| `sketch_add_polyline` | Добавить цепочку отрезков | ✅ реализован | Core | — | — |
| `sketch_add_polygon` | Добавить правильный многоугольник | ✅ реализован | Advanced | — | — |
| `sketch_add_spline` | Добавить сплайн | ✅ реализован | Advanced | — | — |
| `sketch_add_point` | Добавить опорную точку | ✅ реализован | Advanced | — | — |
| `list_sketches` | Перечислить эскизы документа со стабильными идентификаторами и состоянием | ⬜ не реализован | Core | P0 | Нельзя восстановить контекст после открытия документа |
| `describe_sketch` | Прочитать плоскость, состояние и геометрическую сводку эскиза | ⬜ не реализован | Core | P0 | Нет проверки исходного или созданного эскиза |
| `list_sketch_entities` | Перечислить сущности эскиза с типом, геометрией и `entity_id` | ⬜ не реализован | Core | P0 | Созданные объекты не адресуются |
| `update_sketch_entity` | Изменить геометрию сущности по `entity_id` | ⬜ не реализован | Core | P0 | Нет редактирования без пересоздания эскиза |
| `delete_sketch_entity` | Удалить сущность по `entity_id` | ⬜ не реализован | Core | P0 | Нет симметрии create/delete |
| `delete_sketch` | Удалить эскиз с контролем зависимостей | ⬜ не реализован | Core | P0 | Нельзя исправить ошибочно созданный эскиз |
| `list_sketch_constraints` | Перечислить геометрические связи и размеры | ⬜ не реализован | Advanced | P1 | Нет диагностики степени определённости |
| `sketch_set_constraint` | Создать или изменить геометрическую связь | ⬜ не реализован | Advanced | P1 | Нет управляемых зависимостей геометрии |
| `sketch_delete_constraint` | Удалить связь или размер | ⬜ не реализован | Advanced | P1 | Нет полного жизненного цикла ограничений |
| `sketch_set_driving_dimension` | Связать размер эскиза с числом или переменной | ⛔ ограничен платформой | Core | P0 | Надёжная внешняя связь размера с переменной не подтверждена; требуется отдельное техническое исследование или иной режим исполнения |
### 3D-моделирование
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `extrude_boss` | Добавить материал выдавливанием эскиза | ✅ реализован | Core | — | — |
| `extrude_cut` | Удалить материал выдавливанием эскиза | ✅ реализован | Core | — | — |
| `revolve_boss` | Добавить материал вращением профиля | ✅ реализован | Advanced | — | — |
| `revolve_cut` | Удалить материал вращением профиля | ✅ реализован | Advanced | — | — |
| `fillet_edge` | Скруглить одно ребро, выбранное точкой | ✅ реализован | Advanced | — | — |
| `chamfer_edge` | Снять фаску с одного ребра, выбранного точкой | ✅ реализован | Advanced | — | — |
| `fillet_edge_index` | Скруглить одно ребро по текущему индексу | ✅ реализован | Advanced | — | — |
| `chamfer_edge_index` | Снять фаску с одного ребра по текущему индексу | ✅ реализован | Advanced | — | — |
| `shell` | Создать оболочку с удалением граней по текущим индексам | ✅ реализован | Advanced | — | — |
| `rib` | Создать ребро жёсткости по эскизу | ✅ реализован | Advanced | — | — |
| `sweep` | Построить тело перемещением профиля по траектории | ✅ реализован | Advanced | — | — |
| `loft` | Построить тело по сечениям | ✅ реализован | Advanced | — | — |
| `linear_pattern` | Создать линейный массив операций | ✅ реализован | Advanced | — | — |
| `circular_pattern` | Создать круговой массив операций | ✅ реализован | Advanced | — | — |
| `mirror_operation` | Создать зеркальную копию операций | ✅ реализован | Advanced | — | — |
| `mirror_body` | Зеркально скопировать все тела относительно базовой плоскости | ✅ реализован | Advanced | — | — |
| `hole` | Создать простое отверстие | ✅ реализован | Core | — | — |
| `draft` | Создать уклон граней по текущим индексам | ✅ реализован | Advanced | — | — |
| `hole_counterbore` | Создать отверстие с цилиндрическим углублением | ✅ реализован | Advanced | — | — |
| `hole_countersink` | Создать отверстие с коническим углублением | ✅ реализован | Advanced | — | — |
| `hole_conic` | Создать коническое отверстие | ✅ реализован | Advanced | — | — |
| `rebuild` | Перестроить активную модель | ✅ реализован | Core | — | — |
| `create_variable` | Создать переменную модели | ✅ реализован | Advanced | — | — |
| `set_variable` | Изменить выражение переменной и перестроить модель | ✅ реализован | Advanced | — | — |
| `set_variable_note` | Изменить комментарий переменной | ✅ реализован | Optional | — | — |
| `delete_variable` | Удалить переменную с проверкой зависимостей | ✅ реализован | Advanced | — | — |
| `describe_feature` | Прочитать тип, параметры, состояние и зависимости операции по `feature_id` | ⬜ не реализован | Core | P0 | Текущая инспекция не даёт полного адресного контракта |
| `update_feature` | Изменить параметры существующей операции и перестроить модель | ⬜ не реализован | Core | P0 | Нет исторического редактирования |
| `delete_feature` | Удалить операцию с диагностикой зависимостей | ⬜ не реализован | Core | P0 | Нет исправления ошибочного построения |
| `set_feature_suppressed` | Исключить или вернуть операцию в расчёт | ⬜ не реализован | Advanced | P1 | Нет управления вариантами и диагностикой дерева |
| `datum_point_create` | Создать адресуемую вспомогательную 3D-точку по координатам или выбранной геометрии | ⬜ не реализован | Advanced | P1 | Нет опорных точек для последующих построений и измерений |
| `datum_axis_create` | Создать адресуемую вспомогательную ось по точкам, направлению или выбранной геометрии | ⬜ не реализован | Advanced | P1 | Нет произвольных опорных осей вне координатной системы |
| `datum_plane_create` | Создать адресуемую вспомогательную плоскость по трём точкам, оси с углом или выбранной геометрии | ⬜ не реализован | Advanced | P1 | Сейчас доступна только смещённая базовая плоскость внутри создания эскиза |
### Прямое и историческое редактирование
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `move_face` | Сместить грань, выбранную точкой, вдоль нормали | ✅ реализован | Core | — | — |
| `split_solid_by_plane` | Рассечь тела активной детали базовой смещённой плоскостью | ✅ реализован | Advanced | — | — |
| `move_body` | Задать перенос и ориентацию выбранного тела | 🟡 частично | Advanced | P1 | Сейчас доступен только перенос тела по текущему индексу; поворот и явная ориентация отсутствуют |
| `boolean_union` | Объединить все тела активной детали | ✅ реализован | Advanced | — | — |
| `copy_body` | Копировать выбранное тело с преобразованием | ⬜ не реализован | Advanced | P1 | Нет симметричной операции к перемещению и удалению |
| `delete_body` | Удалить выбранное тело | ⬜ не реализован | Core | P0 | Нет полного жизненного цикла многотельной модели |
| `boolean_subtract` | Вычесть выбранные тела-инструменты из целевого тела | ⬜ не реализован | Advanced | P1 | Отсутствует типовая булева операция |
| `boolean_intersect` | Оставить пересечение выбранных тел | ⬜ не реализован | Advanced | P1 | Отсутствует типовая булева операция |
| `body_check_intersection` | Найти пересечения выбранных тел без изменения модели и вернуть пары с оценкой объёма | ⬜ не реализован | Advanced | P1 | Нет аналитической проверки коллизий внутри многотельной детали |
### Инспекция, выбор объектов и измерения
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `describe_model` | Получить структурный паспорт активной модели | ✅ реализован | Core | — | — |
| `list_features` | Перечислить дерево операций и доступные основные параметры | ✅ реализован | Core | — | — |
| `list_bodies` | Перечислить тела по текущим индексам | ✅ реализован | Core | — | — |
| `list_variables` | Перечислить переменные и вычисленные значения | ✅ реализован | Advanced | — | — |
| `describe_face` | Описать грань по текущему индексу | ✅ реализован | Core | — | — |
| `describe_edge` | Описать ребро по текущему индексу | ✅ реализован | Core | — | — |
| `measure` | Измерить расстояние и угол между объектами по переданным индексам, включая вершины | ✅ реализован | Core | — | — |
| `get_part_info` | Получить объём, массу, площадь и центр масс | ✅ реализован | Core | — | — |
| `list_components` | Перечислить компоненты верхнего уровня сборки | ✅ реализован | Core | — | — |
| `list_faces` | Перечислить грани и их типы по текущим индексам | ✅ реализован | Core | — | — |
| `list_edges` | Перечислить рёбра и их типы по текущим индексам | ✅ реализован | Core | — | — |
| `get_bounding_box` | Получить габарит модели | ✅ реализован | Core | — | — |
| `model_snapshot` | Получить растровый снимок модели | ✅ реализован | Optional | — | — |
| `validate_part` | Найти ошибки и необходимость перестроения детали или сборки | ✅ реализован | Core | — | — |
| `get_model_revision` | Получить монотонную ревизию структуры и топологии модели | ⬜ не реализован | Core | P0 | Нужна для обнаружения устаревших ссылок |
| `list_vertices` | Перечислить вершины с координатами и селекторами | ⬜ не реализован | Core | P0 | Замыкает входной контракт измерения вершин |
| `create_selector` | Создать устойчивый геометрический селектор по типу объекта и текущему индексу, вернув также ревизию модели | ⬜ не реализован | Core | P0 | Нет входной точки от списков граней, рёбер, вершин и тел к resolve/validate lifecycle |
| `resolve_selector` | Найти объект по геометрическому селектору и вернуть совпадение в текущей ревизии | ⬜ не реализован | Core | P0 | Нет устойчивого повторного выбора после перестроения |
| `validate_selector` | Проверить, что ссылка относится к текущей ревизии и однозначному объекту | ⬜ не реализован | Core | P0 | Мутации могут примениться не к тому объекту |
### Сборки
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `assembly_add_component` | Вставить деталь или подсборку и задать исходное положение | ✅ реализован | Core | — | — |
| `assembly_add_mate` | Создать адресуемое сопряжение поддерживаемого типа между компонентами | 🟡 частично | Core | P0 | Сейчас доступны только coincidence/distance, выбор по точкам и результат без `mate_id`; нужны также concentric, parallel, perpendicular, angle и tangency |
| `describe_assembly` | Рекурсивно описать дерево компонентов, документы, состояния и placement | ⬜ не реализован | Core | P0 | Верхнеуровневого списка недостаточно для управления |
| `describe_component` | Прочитать компонент, его placement, подавление и ссылки | ⬜ не реализован | Core | P0 | Нет адресной инспекции компонента |
| `assembly_set_component_placement` | Изменить перенос и поворот компонента по `component_id` | ⬜ не реализован | Core | P0 | Нет управления placement после вставки |
| `assembly_remove_component` | Удалить компонент с диагностикой зависимых сопряжений | ⬜ не реализован | Core | P0 | Нет симметрии add/remove |
| `assembly_set_component_suppressed` | Исключить или вернуть компонент в расчёт | ⬜ не реализован | Advanced | P1 | Нет управления вариантами сборки |
| `list_mates` | Перечислить сопряжения, объекты, параметры и состояние решения | ⬜ не реализован | Core | P0 | Созданные сопряжения не адресуются |
| `assembly_update_mate` | Изменить тип, объекты или параметр сопряжения | ⬜ не реализован | Core | P0 | Нет исправления сопряжения без пересоздания |
| `assembly_remove_mate` | Удалить сопряжение по `mate_id` | ⬜ не реализован | Core | P0 | Нет симметрии add/remove |
| `assembly_check_interference` | Найти пересечения компонентов и оценить объём коллизий | ⬜ не реализован | Advanced | P1 | Нет профессиональной проверки физических коллизий; минимальная структурная проверка остаётся в `validate_assembly` |
| `validate_assembly` | Проверить нерешённые, конфликтующие и избыточные сопряжения | ⬜ не реализован | Core | P0 | Текущая общая проверка не даёт сборочный диагноз |
| `specification_generate` | Создать или полностью обновить спецификацию активной сборки по её текущему составу | ⬜ не реализован | Advanced | P1 | Нет выпуска состава изделия; повторный вызов должен детерминированно заменять ранее сгенерированное содержимое |
| `specification_list_items` | Перечислить позиции спецификации с обозначением, наименованием, количеством и ссылкой на компонент | ⬜ не реализован | Advanced | P1 | Нет read-back для проверки сформированного состава |
### Чертежи и оформление
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `drawing_create_standard_views` | Создать набор основных ассоциативных видов и вернуть их номера | ✅ реализован | Core | — | — |
| `drawing_fill_title_block` | Заполнить обозначение, наименование и материал в штампе | ✅ реализован | Core | — | — |
| `drawing_add_linear_dimension` | Создать свободный линейный размер | ✅ реализован | Core | — | — |
| `drawing_add_diametral_dimension` | Создать свободный или связанный с окружностью диаметральный размер | ✅ реализован | Core | — | — |
| `drawing_add_radial_dimension` | Создать свободный или связанный с окружностью радиальный размер | ✅ реализован | Core | — | — |
| `drawing_add_angular_dimension` | Создать свободный угловой размер | ✅ реализован | Core | — | — |
| `drawing_add_rough` | Создать свободное обозначение шероховатости | ✅ реализован | Advanced | — | — |
| `drawing_add_text` | Создать текстовую надпись | ✅ реализован | Core | — | — |
| `drawing_set_technical_requirements` | Заменить блок технических требований | ✅ реализован | Advanced | — | — |
| `drawing_set_sheet_format` | Задать формат и ориентацию листа по номеру | ✅ реализован | Core | — | — |
| `drawing_add_leader` | Создать свободную выноску с одной ветвью и текстом | ✅ реализован | Advanced | — | — |
| `describe_drawing` | Получить структурный паспорт чертежа: листы, виды, основную надпись, технические требования, аннотации и состояние ассоциаций | ⬜ не реализован | Core | P0 | Нет агрегированного read-back без визуального анализа |
| `validate_drawing` | Диагностировать ошибочные или устаревшие виды, разорванные ассоциации и неполное оформление | ⬜ не реализован | Core | P0 | Нет машинной проверки готовности чертежа |
| `drawing_list_sheets` | Перечислить листы, форматы, стили и состояния | ⬜ не реализован | Core | P0 | Нет основы многолистного управления |
| `drawing_add_sheet` | Добавить лист с форматом и стилем | ⬜ не реализован | Advanced | P1 | Многолистный комплект не создаётся |
| `drawing_delete_sheet` | Удалить лист по `sheet_id` | ⬜ не реализован | Advanced | P1 | Нет полного жизненного цикла листа |
| `drawing_list_views` | Перечислить виды с `view_id`, источником, масштабом и placement | ⬜ не реализован | Core | P0 | Созданные виды нельзя надёжно найти и проверить |
| `drawing_describe_view` | Прочитать параметры, границы и состояние обновления вида | ⬜ не реализован | Core | P0 | Нет адресной проверки вида |
| `drawing_update_view` | Изменить масштаб, placement, ориентацию и видимость вида | ⬜ не реализован | Core | P0 | Нет редактирования вида |
| `drawing_delete_view` | Удалить вид и диагностировать зависимые обозначения | ⬜ не реализован | Core | P0 | Нет симметрии create/delete |
| `drawing_rebuild_views` | Обновить связанные виды и вернуть изменившиеся/ошибочные объекты | ⬜ не реализован | Core | P0 | Нет явной проверки актуальности после изменения модели |
| `drawing_create_section_view` | Создать разрез или сечение по линии | ⬜ не реализован | Advanced | P1 | Нет профессионально необходимого типа вида |
| `drawing_create_detail_view` | Создать выносной элемент | ⬜ не реализован | Advanced | P1 | Нет увеличенного локального представления |
| `drawing_create_local_view` | Создать местный вид | ⬜ не реализован | Advanced | P2 | Требуется техническое исследование устойчивой адресации контура |
| `drawing_list_annotations` | Перечислить размеры, обозначения и тексты с `annotation_id` и связями | ⬜ не реализован | Core | P0 | Все созданные аннотации практически не адресуются |
| `drawing_update_annotation` | Изменить положение, текст, стиль или привязку аннотации | ⬜ не реализован | Core | P0 | Нет общего edit-контура |
| `drawing_delete_annotation` | Удалить аннотацию по `annotation_id` | ⬜ не реализован | Core | P0 | Нет общего delete-контура |
| `drawing_add_datum` | Создать обозначение базы и вернуть `annotation_id` | ⬜ не реализован | Advanced | P1 | Нет оформления баз |
| `drawing_add_geometric_tolerance` | Создать рамку допуска формы или расположения с базами | ⬜ не реализован | Advanced | P1 | Нет оформления допусков |
| `drawing_add_centerline` | Создать осевую линию с привязкой к геометрии | ⬜ не реализован | Advanced | P1 | Нет осевых линий |
| `drawing_add_center_mark` | Создать обозначение центра окружности или дуги | ⬜ не реализован | Advanced | P1 | Нет центровых обозначений |
### Импорт и экспорт
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `import_step` | Импортировать STEP как новую активную деталь или сборку | ✅ реализован | Core | — | — |
| `export_step` | Экспортировать активную 3D-модель в STEP | ✅ реализован | Core | — | — |
| `import_model` | Импортировать нейтральный или собственный 3D-формат, кроме STEP, по явному `format` | ⬜ не реализован | Advanced | P2 | STEP принимается только специализированным `import_step`; остальные форматы требуют поформатной проверки |
| `export_model` | Экспортировать документ или выбранные тела в 3D-формат, кроме STEP | ⬜ не реализован | Advanced | P2 | STEP выпускается только специализированным `export_step`; для остальных форматов нужна проверяемая матрица возможностей |
| `import_drawing` | Импортировать 2D-обменный формат в новый документ | ⬜ не реализован | Advanced | P2 | Нужна поформатная проверка слоёв, масштаба и единиц |
| `export_drawing` | Экспортировать выбранные листы в единый PDF-документ | ⬜ не реализован | Advanced | P1 | Нет выпуска комплекта чертежей для просмотра и печати |
| `export_drawing_exchange` | Экспортировать геометрию выбранного листа в редактируемый 2D-обменный формат | ⬜ не реализован | Advanced | P2 | Нужна отдельная поформатная проверка слоёв, масштаба, шрифтов и единиц |
| `export_mesh` | Экспортировать выбранные тела в полигональный формат с параметрами точности | ⬜ не реализован | Optional | P2 | Требуется проверка доступных форматов и единиц |
### Управление состоянием, восстановление и надёжность агентской работы
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
| `transaction_begin` | Начать атомарную группу изменений | ⛔ ограничен платформой | Optional | P2 | Надёжная атомарная граница не подтверждена; требуется техническое исследование |
| `transaction_commit` | Зафиксировать атомарную группу изменений | ⛔ ограничен платформой | Optional | P2 | Реалистичность зависит от подтверждения общей транзакционной модели |
| `transaction_rollback` | Откатить атомарную группу изменений | ⛔ ограничен платформой | Optional | P2 | Надёжный полный откат группы не подтверждён; требуется техническое исследование |
| `state_checkpoint` | Создать детерминированный именованный снимок состояния; дубликат имени отклоняется, а `replace=true` явно заменяет его | ⬜ не реализован | Core | P0 | Нужен подтверждаемый рубеж с однозначной политикой имён перед рискованной операцией |
| `state_restore` | Восстановить документ из снимка состояния и вернуть новую ревизию | ⬜ не реализован | Core | P0 | Нет детерминированного fallback после неверной цепочки мутаций |
| `state_list_checkpoints` | Перечислить снимки состояния документа с именем, временем, исходной ревизией и признаком доступности | ⬜ не реализован | Core | P0 | Нельзя обнаружить допустимые точки восстановления |
| `state_delete_checkpoint` | Удалить именованный снимок состояния без изменения документа | ⬜ не реализован | Core | P0 | Нет завершения lifecycle и очистки устаревших снимков |
| `undo` | Отменить последнее изменение указанного документа | ⬜ не реализован | Core | P0 | Нет пошагового исправления |
| `redo` | Повторить отменённое изменение указанного документа | ⬜ не реализован | Core | P0 | Нет симметрии undo/redo |
| `operation_cancel` | Запросить отмену длительной операции и сообщить фактический результат | ⛔ ограничен платформой | Optional | P2 | Надёжная остановка уже начавшейся операции не подтверждена; требуется техническое исследование |
| `session_recover` | Восстановить внешний контекст после сбоя: документы, активность, ревизии и адреса | ⬜ не реализован | Core | P0 | Сессионные идентификаторы теряются при сбросе или переподключении |
## Приоритизированный перечень пробелов
### P0 — замыкание автономного цикла
- Документы: `list_documents`, `document_activate`, `document_describe`.
- Эскизы: `list_sketches`, `describe_sketch`, `list_sketch_entities`, `update_sketch_entity`, `delete_sketch_entity`, `delete_sketch`, `sketch_set_driving_dimension`.
- История и тела: `describe_feature`, `update_feature`, `delete_feature`, `delete_body`.
- Устойчивый выбор: `get_model_revision`, `list_vertices`, `create_selector`, `resolve_selector`, `validate_selector`.
- Сборки: `assembly_add_mate`, `describe_assembly`, `describe_component`, `assembly_set_component_placement`, `assembly_remove_component`, `list_mates`, `assembly_update_mate`, `assembly_remove_mate`, `validate_assembly`.
- Чертёжный CRUD: `describe_drawing`, `validate_drawing`, `drawing_list_sheets`, `drawing_list_views`, `drawing_describe_view`, `drawing_update_view`, `drawing_delete_view`, `drawing_rebuild_views`, `drawing_list_annotations`, `drawing_update_annotation`, `drawing_delete_annotation`.
- Восстановление: `state_checkpoint`, `state_restore`, `state_list_checkpoints`, `state_delete_checkpoint`, `undo`, `redo`, `session_recover`.
### P1 — типовые профессиональные сценарии
- Документы и эскизы: `document_set_properties`, `list_sketch_constraints`, `sketch_set_constraint`, `sketch_delete_constraint`.
- История и многотельность: `set_feature_suppressed`, `move_body`, `copy_body`, `boolean_subtract`, `boolean_intersect`, `body_check_intersection`.
- Вспомогательная 3D-геометрия: `datum_point_create`, `datum_axis_create`, `datum_plane_create`.
- Сборки и спецификации: `assembly_set_component_suppressed`, `assembly_check_interference`, `specification_generate`, `specification_list_items`.
- Профессиональное оформление: `drawing_add_sheet`, `drawing_delete_sheet`, `drawing_create_section_view`, `drawing_create_detail_view`, `drawing_add_datum`, `drawing_add_geometric_tolerance`, `drawing_add_centerline`, `drawing_add_center_mark`.
- Выпуск: `export_drawing`.
### P2 — расширение охвата
- Дополнительный вид: `drawing_create_local_view`.
- Дополнительные форматы: `import_model`, `export_model`, `import_drawing`, `export_drawing_exchange`, `export_mesh`.
- Исследовательские операции состояния: `transaction_begin`, `transaction_commit`, `transaction_rollback`, `operation_cancel`.
## Итоговая оценка
Текущий контракт силён в создании 3D-геометрии, структурном чтении детали, STEP-обмене и первичном создании сборок и чертежей. Он ещё не является полным автономным CAD-контуром: активный документ остаётся глобальным неявным состоянием; созданные сущности эскиза, отверстия, компоненты, сопряжения, виды и аннотации недостаточно адресуемы; индексы топологии не связаны с ревизией; отсутствуют update/delete для большинства семейств и безопасное восстановление. Все пять сквозных сценариев поэтому остаются частичными или неполными.
File diff suppressed because one or more lines are too long
-115
View File
@@ -1,115 +0,0 @@
# План реализации MCP-сервера КОМПАС-3D
> Документ описывает, **как** строить сервер. Что строим и почему — в
> [`ARCHITECTURE.md`](ARCHITECTURE.md). Прогресс отражается в
> [`presentation.html`](presentation.html) (TODO).
## Принцип
**«Ходячий скелет» + спайки на риски.** Сначала сквозной тонкий срез
(подключение → снимок), который проходит через все слои и доказывает, что связка
COM ↔ STA ↔ MCP работает. Затем наращиваем инструменты, и **каждый шаг построения сразу
проверяем визуально** (`model_snapshot`) — это и есть целевой UX «строю под контролем».
## Окружение (проверено 2026-05-25)
| | Факт | Следствие для плана |
|---|---|---|
| .NET SDK | **10.0.108** (отдельного .NET 8 SDK нет; рантайм 8 есть) | таргет **`net8.0-windows`** (LTS), собирается SDK 10 |
| ProgID КОМПАС | **`KOMPAS.Application.7`** и **`KOMPAS.Application.5`** зарегистрированы; `KOMPASLT.*` **отсутствует** | подключаться напрямую к API7 через `KOMPAS.Application.7`; LT — только запасной |
| Процесс | КОМПАС не запущен | спайк подключения должен уметь **launch**, не только attach |
| SDK | `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK` (`lib\*.tlb`) | источник interop |
**Прерэквизит разработчика:** установленный КОМПАС v24 + возможность его запускать.
---
## Спайки (делать первыми — снимают главные неизвестные)
| # | Риск / вопрос | Критерий успеха |
|---|---|---|
| **S1** | Генерация COM-interop из `.tlb` в SDK-проекте | `net8.0-windows` проект с `<COMReference>` на `kAPI7.tlb`, `kAPI5.tlb`, `ksConstants.tlb`, `ksConstants3D.tlb` собирается; типы `IApplication`, `KompasObject` доступны. Запасной путь — `tlbimp`/ручные interop-сборки |
| **S2** | Подключение по ProgID | консольная проба: attach (`Marshal.GetActiveObject`) → при отказе launch (`Activator.CreateInstance(Type.GetTypeFromProgID("KOMPAS.Application.7"))`); получить `IApplication`, `Visible=true`, прочитать версию |
| **S3** | STA-поток + насос сообщений + async MCP | выделенный STA-поток выполняет COM-вызовы из пула задач без `RPC_E_WRONGTHREAD`; насос сообщений не вешает процесс |
| **S4** | Рендер модели в растр на v24 Home | `ksDocument3D.RasterFormatParam()``resultArrayBytes` отдаёт корректные PNG-байты открытого 3D-документа |
Спайки — выбрасываемый код в `spikes/`; выводы переносятся в `Core`.
---
## Фазы
### Фаза 0 — Каркас репозитория
- `git init`, `.gitignore` (.NET + `bin/`, `obj/`; **не** игнорировать `docs/`).
- `KompasMcp.sln`; проекты `Kompas.Mcp.Host`, `Kompas.Mcp.Core`, `Kompas.Mcp.Interop`, `Kompas.Mcp.Tools`
(в v1 допустимо начать одним проектом, разнося по папкам).
- `Directory.Build.props`: `TargetFramework=net8.0-windows`, `Platforms=x64`, `Nullable=enable`, `LangVersion=latest`.
- Пакеты: `ModelContextProtocol`, `Microsoft.Extensions.Hosting`.
- **Готово:** `dotnet build -c Release` проходит; пустой stdio-хост стартует.
### Фаза 1 — Interop + маппинг констант
- `<COMReference>` на типобиблиотеки (итог S1). Зафиксировать стратегию (embed vs сборки).
- `Kompas.Mcp.Interop`: хелперы «строка ↔ enum» — `DocumentTypeEnum` (part=4, assembly=5, drawing=1, fragment=2),
`Obj3dType`, `ksEndTypeEnum`, `ksDirectionTypeEnum`, базовые плоскости (`o3d_planeXOY/XOZ/YOZ`).
- **Готово:** unit-тесты маппинга (без COM) зелёные.
### Фаза 2 — STA-диспетчер
- `KompasThread`: выделенный STA-поток, очередь задач (`Channel`/`BlockingCollection`),
`Task<T> InvokeAsync(Func<T>)`, насос сообщений + периодический `PumpWaitingMessages` (итог S3).
- **Готово:** интеграционный тест — параллельные `InvokeAsync` исполняются строго на одном STA-потоке.
### Фаза 3 — Connection + вертикальный срез (MCP)
- `KompasSession`: resolve ProgID (`KOMPAS.Application.7``.5`+`ksGetApplication7()``KOMPASLT.*`),
attach/launch, `Visible`, версия/редакция, освобождение COM, `Quit` только если запускали мы.
- Хост: stdio-сервер, **логи в stderr**, регистрация инструментов через DI.
- Инструменты `System`: `kompas_connect`, `kompas_status`, `kompas_set_visible`.
- **Готово (веха «скелет»):** из MCP Inspector вызвать `kompas_connect` → КОМПАС стартует и виден; `kompas_status` отвечает.
### Фаза 4 — Documents
- `document_create` (part/assembly/drawing/fragment), `document_open`, `document_save`, `document_close`, `document_active`.
- **Готово:** создать деталь, сохранить `.m3d`, закрыть, открыть заново.
### Фаза 5 — Vision (раннее — делает всё дальнейшее проверяемым)
- `set_view` (ориентация iso/front/top/right, zoom-to-fit, `ShadedWireframe`), `model_snapshot`
(`RasterFormatParam.resultArrayBytes` → image-контент MCP) — итог S4.
- **Готово:** `model_snapshot` возвращает PNG открытой детали; агент видит изображение в ответе инструмента.
### Фаза 6 — Sketch
- `sketch_create` (плоскость или грань), `sketch_add_line|circle|arc|rectangle`, `sketch_close`
(`NewEntity(o3d_sketch)``SetPlane``BeginEdit`/примитивы/`EndEdit`, гарантированный `EndEdit` в `finally`).
- **Готово:** построить эскиз-окружность на XOY и увидеть его на снимке.
### Фаза 7 — Features
- `extrude_boss`/`extrude_cut` (depth, direction, endType), `revolve_boss`/`revolve_cut`, `rebuild`.
- **Готово (целевой демо-сценарий):** окружность → `extrude_boss` (цилиндр) → `model_snapshot`
выбрать грань → эскиз → `extrude_cut` (отверстие) → `model_snapshot`. Полный цикл со зрением.
### Фаза 8 — Query / Selection
- `select_face_by_point`, `list_bodies`, `list_faces_planes`, `get_part_info` (МЦХ/габариты).
- **Готово:** агент может выбрать грань для следующего эскиза по координатам.
### Фаза 9 — Качество и поставка
- Unit-тесты (маппинг, валидация, форматирование); интеграционные (`[Trait("Category","Integration")]`, гейтятся).
- Трансляция ошибок COM (`ksGetLastError`/`IApplication.KompasError`) в структурированные MCP-ошибки.
- Сборка `kompas-mcp.exe` (`dotnet build -c Release`; платформа x64 — из `Directory.Build.props`), регистрация stdio-сервера в конфиге клиента.
- Escape-hatch `run_kompas_command` (опц.).
---
## Порядок и зависимости
```
S1,S2 ─► Фаза 0 ─► Фаза 1 ─► Фаза 2 ─► Фаза 3 (скелет) ─► Фаза 4 ─► Фаза 5 (зрение)
Фаза 6 ─► Фаза 7 ─► Фаза 8 ─► Фаза 9
S3 ─► Фаза 2 S4 ─► Фаза 5
```
Критический риск — **S1/S3** (interop + STA). Если `<COMReference>` не заработает на v24 `.tlb`
переход на сгенерированные `tlbimp` interop-сборки. Все спайки до Фазы 3.
## Definition of Done для v1
Агент через MCP: подключается к КОМПАС → создаёт деталь → строит эскиз → выдавливает →
делает снимок → видит результат → выбирает грань → повторяет → сохраняет `.m3d`.
Сервер собирается в один `.exe` и регистрируется в клиенте по stdio.
-286
View File
@@ -1,286 +0,0 @@
# Открытые вопросы для проработки
Журнал спорных моментов и решений, принятых автономно (ночная сессия 2026-05-25/26).
Помечено: ⚠️ = требует вашего решения; ✅ = решено по умолчанию, можно пересмотреть.
---
## ⚠️ 1. API5 vs API7 для построения 3D
**Контекст.** Примеры SDK строят 3D через **API5** (`ksPart.NewEntity(o3d_*)`
`ksBossExtrusionDefinition``Create()`). API7 предлагает `IPart7``IModelContainer`
`IExtrusions.Add()`.
**Решение по умолчанию (✅ в коде):** в v1 строю 3D через **API5 `ksPart`** (надёжно, повторяет
рабочие примеры Step3d1). API7 использую для приложения/документов/версии.
**На проработку:** переводить ли операции на чистый API7 позже (плюс — единообразие, минус — риск).
## ✅ 2. Точка входа подключения — API5
Для построения 3D нужен `KompasObject` (API5), а для снимка — `ksDocument3D` (API5).
Поэтому `KompasSession` подключается через `KOMPAS.Application.5``KompasObject`, затем
`ksGetApplication7()``IApplication`. Держит обе ссылки. (Ранее в спайке заходили через `.7`.)
## ✅ 3. Состав проектов v1
Вместо 4 проектов (Host/Tools/Core/Interop) сделано компактно: **Core** (COM + сервисы) +
**Host** (MCP + определения инструментов) + **Tests**. Interop — вендорские DLL в `libs/`.
Разнести на больше проектов можно позже.
## ✅ 4. Тестовые артефакты
Тестовые документы КОМПАС сохраняю в `.scratch/` (gitignore). Снимки рендера — туда же,
для визуальной проверки.
---
## Само-ревью кода (ночная сессия) — итоги
**Исправлено сразу:**
- C1/C2 — утечки COM и устаревшие id: `PartModeler` освобождает RCW и сбрасывает реестр
(`ResetAsync`/`Dispose`), `DocumentTools` сбрасывает реестр при create/open/close;
`DocumentService` освобождает транзитные `IDocuments`/`IKompasDocument`.
- M3 — `KompasSession.Dispose`: ограниченное ожидание (5 c) вместо вечного блока.
- M4 — `ExtrudeAsync`: убран `dynamic`, конкретные `ksBossExtrusionDefinition`/
`ksCutExtrusionDefinition`; `throughAll` разрешён только для выреза.
- m7 — `extrude_cut`: дефолт `throughAll=false`, без молчаливой подмены `depth`.
- Регрессионный тест: после `ResetAsync` старый id эскиза недействителен.
**⚠️ Отложено на проработку (из ревью):**
- M5 — отмена/насос сообщений: `CancellationToken` не прерывает уже идущий COM-вызов; зависший
вызов (модальный диалог КОМПАС) блокирует всю очередь. Возможное решение — диспетчер с насосом
сообщений + подавление диалогов. Связано с [конкурентностью MCP].
- m6 — `colorBPP=24` для снимка: для GIF (палитра) может не подойти; снимок по умолчанию PNG.
- m8 — приведения enum к `short` для `GetPart/NewEntity/GetDefaultEntity`: на v24 работает
(тесты зелёные), но стоит сверить точные типы параметров в interop.
- m10 — `_app/_kompas` читаются из `IsConnected/Status()` вне STA-потока без `volatile`
(доброкачественное устаревшее чтение).
- m11 — трансляция `COMException`/HRESULT в понятные сообщения (сейчас понятны только наши
`InvalidOperationException`); добавить текст последней ошибки КОМПАС.
### Ревью v2 (грань/вращение/скругление/фаска/габариты/список граней)
**⚠️ Отложено:**
- v2-1 — **направление операций на грани**: для бобышки/выреза на выбранной грани параметр
`forward` зависит от ориентации нормали грани, которая для эскиза-на-грани оказывается
направленной *внутрь* тела (boss с `forward=true` уходит в материал — прироста нет;
наружу даёт `forward=false`). Сейчас выбор направления — на стороне агента (по снимку).
На проработку: автоопределение «наружу» по нормали грани, либо опция `auto`.
- v2-2 — **утечки транзитных RCW в `PartModeler`**: `GetTopPart()` и коллекции
`EntityCollection(o3d_face/o3d_edge)` не освобождаются (унаследованный паттерн; `QueryService`
их освобождает, `PartModeler` — только зарегистрированные сущности в `ResetCore`). Накапливается
за длинную сессию. На проработку: единый `using`/release транзитов в построителе.
Тот же паттерн у новых членов (`SelectFaceByIndex`, `ShellAsync`: транзитные `ksShellDefinition`/
`FaceArray()`/коллекции граней не освобождаются — сознательно, ради консистентности с
`Fillet`/`Chamfer`; войдёт в общий рефакторинг release транзитов).
- v2-3 — **стабильность индексов граней** (`list_faces``sketch_create_on_face_index`):
индекс валиден, пока геометрия не меняется между вызовами. После любой операции порядок/состав
граней может измениться — переиспользовать старый индекс нельзя, нужно перезапросить `list_faces`.
- v2-4 — `fillet/chamfer` строятся по одному ребру (первое из `SelectByPoint`/коллекции);
`tangent=false`. На проработку: набор рёбер, скругление по касательным, переменный радиус.
**✅ Решено по ходу:**
- Угол вращения `revolve_*` — в градусах, `directionType=dtNormal`, `toroidShapeType=сфероид`,
`SetThinParam(false,…)` для сплошного тела. Ось вращения — осевая линия (системный стиль 3).
- Габариты — `ksPart.GetGabarit(full=false)` (только тела), координаты в мм.
## Журнал решений по ходу
(дополняется автоматически во время работы)
### ⚠️ Конкурентность вызовов MCP и состояние сессии
MCP-сервер обрабатывает запросы **конкурентно**. Цикл моделирования с реестром эскизов по id
(`sketch_create``sketch_add_*`) предполагает, что клиент **ждёт ответа** на каждый вызов перед
следующим (нормальное поведение LLM-клиентов). При «конвейерной» отправке зависимых вызовов
возможна гонка (id ещё не зарегистрирован). STA-диспетчер сериализует COM, но не порядок постановки задач.
**На проработку:** нужна ли серверная сериализация вызовов инструментов (очередь) для надёжности
stateful-сессии? Пока полагаемся на последовательное поведение клиента.
### ✅ Возврат изображения: ImageContentBlock.FromBytes
Поле `ImageContentBlock.Data` хранит **base64-байты**, а не сырые. Сырые байты PNG нужно
передавать через `ImageContentBlock.FromBytes(bytes, mimeType)` — он сам кодирует. Прямое
присваивание `Data = rawBytes` портит изображение на проводе. Исправлено в VisionTools.
### ✅ МЦХ (get_part_info): API5 `CalcMassInertiaProperties`, а не API7
Сначала пробовали API7 `IMassInertiaParam7` (от `TopPart`). Две проблемы:
1. **Единицы** — отдаёт в единицах отображения МЦХ документа (по умолчанию см³/см²/г), зависит от настроек.
2. **Залипающий кэш** — после первого расчёта свойство `Actual` остаётся `TRUE` и не сбрасывается
при изменении геометрии через API5; `Calculate()` возвращает `FALSE` и не пересчитывает.
Сценарий «запрос → достройка → запрос» отдавал **устаревшее** значение. Сброс через
`RebuildModel`, переключение `MassSettingMode` — не помогли.
**Решение (✅ в коде):** считаем через API5 **`ksPart.CalcMassInertiaProperties(bitVector)`** →
`ksMassInertiaParam` (`v`/`m`/`F`/`xc`/`yc`/`zc`). Считает «по запросу» (без залипания), единицы
задаются битовым вектором: `ST_MIX_MM (0x1) | ST_MIX_KG (0x10)`**мм³, мм², кг, мм**.
Согласовано с тем, что деталь строится через API5 `ksPart`. Проверено двумя тестами:
цилиндр R10×H20 (V≈6283 мм³, S≈1885 мм², m≈0.0494 кг, Zc=10 мм) и «запрос → штифт на грани → запрос»
(виден корректный прирост ≈785 мм³). Материал по умолчанию — сталь (ρ≈7.856 г/см³).
### ✅ Снимок 3D: рендер через файл, а не resultArrayBytes
`ksDocument3D.SaveAsToRasterFormat(fileName, param)` при **непустом** `fileName` возвращает TRUE,
пишет корректный PNG на диск, но `param.resultArrayBytes` остаётся **пустым**. Поэтому
`SnapshotService` рендерит во временный файл и читает байты обратно (надёжно проверено на v24 Home).
**На проработку:** пустое имя файла + `returnResultAsArrayBytes=true` для чистого in-memory —
не проверено; текущий путь через temp-файл работает.
### ✅ STEP импорт/экспорт и list_components — реализованы
`import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (`QueryService.ListComponentsAsync`) добавлены в v2+.
Проверенные COM-приёмы:
- Импорт: `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (код формата, не путь к DLL) → `IConverter.ConverterParameters(cmd)``IAdditionConvertParameters.Format=ksConverterFromSTEP``IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`.
- Экспорт: `ConvertToAdditionFormat` с форматами AP203/AP214/AP242 (`StepFormat` enum в `Core/Conversion/StepFormat.cs`).
- Param-коклассы (AdditionConvertParameters) — **не** CoCreatable, только через фабрики.
- Обход сборки: `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`.
- Извлечение детали: `prm.NeedCreateComponentsFiles=true` + `IPart7.OpenSourceDocument`.
### ✅ Прямое редактирование импортированной B-rep — move_face реализован
**Решено.** Перемещение грани продуктизировано как инструмент `move_face` / сервис `FaceEditService` (`src/Kompas.Mcp.Core/Editing/FaceEditService.cs`). Зарегистрирован в DI (`Program.cs`); интеграционный тест — `Integration/FaceEditTests.cs`.
Проверенный паттерн:
- Объект грани берётся через **`IPart7.FindObjectsByPoint(x, y, z, true)`** → результат приводится к `KompasAPI7.IFace`. Не требует `EntityCollection`/`SelectByPoint` через API5.
- Контейнеры операций не создаются отдельно: `(ISurfaceContainer)part` и `(IModelContainer)part` — COM-QI от `IPart7` в рантайме.
- `FaceMover`: `SetFaces(faces)` + `Offset(distance)` + `Direction(true/false = наружу/внутрь)` + `Update()`.
- Работает как на параметрической геометрии, так и на импортированной B-rep (проверено: деталь top_spacer, высота 39.45 → 41.45 мм).
Полный «рассечь→раздвинуть→объединить» (`SplitSolids`/`BodyRepositions`) — механика есть и драйвится, но отдельными MCP-инструментами не оформлена (возможная будущая опция).
### ✅ Структурный осмотр модели — инспекция реализована
**Решено.** 7 инструментов (`InspectionTools.cs`, `ModelInspectionService.cs`, `InspectionText.cs`):
- `describe_model` — структурный «паспорт» детали: габарит, МЦХ, тела, сводка топологии, дерево операций с параметрами, переменные. **Предпочтителен перед `model_snapshot`**.
- `list_features` / `list_bodies` / `list_variables` — детальные срезы.
- `describe_face` / `describe_edge` — drill-down по индексу.
- `measure` — расстояние/угол между объектами (`ksMeasurer`).
Ключевые паттерны: дерево — `ksPart.GetFeature()``SubFeatureCollection(true,false)`; тела — `BodyCollection()``ksBody`; переменные — `VariableCollection()``ksVariable`. Детект «импорт без истории»: `bodyCount>0 && нет формообразующих операций && features.Count<=bodyCount+1`. Итог: **62 инструмента, 101 тест.**
### ✅ hole — реализован через API7
**Решено.** Инструмент `hole` добавлен через API7 — в API5 интерфейс `ksHoleDefinition` отсутствует в interop (`o3d_holeOperation=52` есть, но определения нет). Новый сервис `HoleService` (`src/Kompas.Mcp.Core/Modeling/HoleService.cs`) не является методом `PartModeler`.
Паттерн размещения: `(IModelContainer)part7``Points3D.Add()` (создаёт `IPoint3D` как центр) → `Holes3D.Add()``IHole3D``(IHoleDisposal)hole` (`BaseSurface` = грань по `FindObjectsByPoint`, `Perpendicular=true`, `AssociationVertex=point`) → `Update()``RebuildDocument()`. Эскиз размещения не нужен.
**Ключевая тонкость — направление по объёму:** `Update()` возвращает TRUE даже если сверлит «в воздух» снаружи тела (убыли материала нет). Решение: попытка `Direction=true` → Rebuild → сравниваем объём через API5 `CalcMassInertiaProperties`; если не убыл — пробуем `Direction=false`. Тот же класс проблем, что у boss/cut на грани (непредсказуемая ориентация нормали). При неуспехе обеих попыток — откат через `IFeature7.Delete()`. Добавлена валидация `double.IsFinite` (Infinity проходил мимо `>0`).
Отверстие не попадает в реестр `_features` API5 → возвращает подтверждение без id. +3 интеграционных теста (`HoleTests.cs`).
### ✅ draft (уклон) — реализован через API5 Incline, а не API7
**Решено.** Ранее считалось, что уклон недоступен в API5 и требует API7. Это было заблуждением: поиск вёлся по слову "Draft", тогда как операция в API5 называется **Incline** (`ksInclineDefinition`, `o3d_incline=42`). Реализация — стандартный API5-паттерн `PartModeler` (как shell/rib/loft): `NewEntity(o3d_incline)``ksInclineDefinition``FaceArray()` (грани по индексам) → `SetPlane(координатная плоскость)``angle``direction``Create()` → регистрация в `_features`.
**Ключевая тонкость (эмпирика):** `direction=false`=расширение (outward), `direction=true`=сужение — **обратно справке**. Маппинг в коде: `def.direction = !outward`. Тот же класс проблем с направлением, что у boss/cut (v2-1). Валидация угла вынесена в `SketchGeometry.RequireDraftAngle(0<angle<90)`. +9 unit-тестов + 2 integration-теста. Итог: **64 инструмента, 115 тестов (69 unit + 46 integration).**
Примечание: `o3d_DraftFromEdges=644` / `IDraftFromEdges` — это другая операция «уклон от базовой линии» (API7), здесь не используется.
### ✅ Пакет D «параметрика» — CRUD переменных реализован
**Решено (start).** 3 новых инструмента: `create_variable`, `set_variable`, `delete_variable``VariableService` (`src/Kompas.Mcp.Core/Modeling/VariableService.cs`) + `VariableTools` (`src/Kompas.Mcp.Host/Tools/VariableTools.cs`). Исправлен `list_variables` в `ModelInspectionService.ReadVariables`. Итог (на тот момент): **67 инструментов, 116 тестов (69 unit + 47 integration).**
**Ключевые API-тонкости (эмпирика):**
- Создание переменной — ТОЛЬКО через `ksPart.GetFeature().VariableCollection` (свойство-коллекция на корневом `ksFeature`). `ksPart.VariableCollection()` (метод) возвращает только **внешние** переменные и непригоден для создания пользовательских.
- Expression — ведущее поле; Value — вычисленный результат. Изменяют значение через Expression.
- После `ksPart.RebuildModel()` RCW `ksVariable` застревает на старом значении → перечитывать из свежей коллекции (`ReadValueFresh`). Зависимые переменные пересчитываются автоматически.
- `RemoveVariable` возвращает FALSE при наличии зависимых переменных → удаление невозможно.
- `list_variables` исправлен: ранее читал `ksPart.VariableCollection()` (только внешние), теперь `ksPart.GetFeature().VariableCollection` (все), с фолбэком на старый путь.
**⚠️ Осознанное ограничение — геометрия не меняется:** переменная управляет геометрией только в параметрической модели, где размеры эскизов привязаны к именам переменных. Наши эскизы строятся литеральными координатами — `set_variable` хранит и вычисляет значение, но геометрию НЕ меняет.
Связь размеров эскизов с переменными (параметрические эскизы через API2D) — отдельный пласт, **не реализован**. Это осознанно задокументировано; реализация — продолжение пакета D.
### ✅ Параметрические эскизы (пакет D продолжение) — исследовано, недоступно через COM-API
**Исследовано и закрыто.** `ksCDimWithVariable` (размер с переменной) не конструируется из внешней автоматизации — интерфейс недоступен в COM-клиентском режиме. Связать размеры эскиза с именем переменной через API2D невозможно без исполнения кода внутри процесса КОМПАС (plugin). Подробности — `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`.
### ✅ Отверстие с цековкой и зенковкой — реализовано
**Решено.** 2 новых инструмента: `hole_counterbore` (`ksHTCounterbore`, `ISpotfacingHoleParameters`) и `hole_countersink` (`ksHTCountersinking`, `ICountersinkHoleParameters`). `HoleCore` рефакторен под `configure`-колбэк. Итог: **69 инструментов, 118 тестов (69 unit + 49 integration).**
### ✅ Коническое отверстие — реализовано
**Решено.** Новый инструмент `hole_conic` — коническое (конусное) отверстие, сужающееся вглубь. Параметры: `x,y,z`, `diameter` (диаметр у основания), `conicAngle` (угол конуса, 0<angle<180), `depth=0`, `throughAll=false`. Реализован через тот же `HoleCore`-паттерн (`configure`-колбэк): `HoleType=ksHTConic` + configure устанавливает `(IConicHoleParameters)hole.HoleParameters` (`ConicType=ksCNAngle`, `ConicAngle`). Завершает набор типов отверстий: простое (`hole`), цековка (`hole_counterbore`), зенковка (`hole_countersink`), коническое (`hole_conic`). Итог: **70 инструментов, 119 тестов (69 unit + 50 integration).**
### ✅ Вставка компонента в сборку — реализована (начало класса «Сборки»)
**Решено.** Новый инструмент `assembly_add_component(filePath, x, y, z)` — первый инструмент, который *пишет* в сборку (ранее `list_components` только читал). Сервис `AssemblyService` (`src/Kompas.Mcp.Core/Assemblies/`), инструменты — `AssemblyTools`. Паттерн API7: `IParts7.AddFromFile``IPlacement3D.SetOrigin``UpdatePlacement``RebuildModel(true)`. Ключевые тонкости: `UpdatePlacement` возвращает FALSE для вручную позиционируемого компонента — не ошибка; зафиксированный первый компонент двигается через `SetOrigin+RebuildModel` без разфиксации; `RequireActiveAssembly` проверяет тип документа до `TopPart`. Откат при неуспехе через `IFeature7.Delete`.
### ✅ Сопряжения в сборках — реализованы (coincidence/distance)
**Решено (инкремент 2).** Новый инструмент `assembly_add_mate(mateType, x1,y1,z1, x2,y2,z2, value=0)`. Паттерн API7: `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)``BaseObject1/2` (грани через `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue``Update()``RebuildModel(true)`; проверка `mate.Valid`. Типы `mc_Coincidence=0`/`mc_Distance=5` проверены вживую. Enum `MateType` + static `Mates` в `src/Kompas.Mcp.Core/Assemblies/MateType.cs`. Итог: **72 инструмента, 156 тестов (98 unit + 58 integration).** Следующий шаг: прочие типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`).
### ✅ Стандартные виды чертежа — реализованы (начало вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_create_standard_views(partFilePath, scale, x, y)` — создаёт ассоциативные виды (спереди/сверху/слева) на активном чертеже. Реализовано через API7 `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`), `DrawingTools`. Паттерн: `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, 20, 20)``object[]` как SAFEARRAY VT_I4. Проверено вживую: `Views.Count` 1→4, `ObjectCount>0` сразу (rebuild не нужен).
### ✅ Основная надпись (штамп) чертежа — реализована (инкремент 2 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_fill_title_block(designation?, name?, material?)` — заполняет графы основной надписи активного чертежа. Паттерн API7: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text``stamp.Update()`. `IText.Str` замещает содержимое напрямую (повторная запись перезаписывает — `Clear` не нужен, подтверждено round-trip тестом). Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`; инструмент в `DrawingTools.cs`. Итог: **74 инструмента, 188 тестов (123 unit + 65 integration).** Следующий шаг: размеры на видах.
### ✅ Линейные размеры на видах чертежа — реализованы (инкремент 3 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — ставит линейный размер на виде активного чертежа. Паттерн API7: получить вид по `viewNumber` → COM-QI `(ISymbols2DContainer)view` (размер адресуется конкретному виду, а не листу) → `LineDimensions.Add()``ILineDimension``X1,Y1,X2,Y2` (выносные точки в **локальной СК вида**, мм), `X3,Y3` (положение размерной линии), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true``Update()``Valid` → значение `((IDimensionText)dim).NominalValue`. Откат `dim.Delete()` при ошибке или нулевом значении. **Ключевая находка: размеры НЕ входят в `IView.ObjectCount`** (тот считает геометрию `IDrawingContainer`); размеры живут в `ISymbols2DContainer` отдельно — попадание размера в вид проверяется через `ISymbols2DContainer.LineDimensions.Count`. `DrawingViewsResult` расширен полем `ViewNumbers` (номера созданных видов — для адресации при простановке размеров). Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. Итог: **75 инструментов, 203 теста (131 unit + 72 integration).** Следующий шаг: диаметральные/радиальные/угловые размеры, рамка/формат.
### ✅ Диаметральный размер на виде чертежа — реализован (инкремент 4 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_add_diametral_dimension(xc,yc,radius, angle, viewNumber)` — ставит диаметральный размер (Ø) на виде активного чертежа. Паттерн API7: общий хелпер `RequireSymbols2DContainer(viewNumber)` (переиспользуется с линейным путём) → `DiametralDimensions.Add()``IDiametralDimension` (`Xc`, `Yc`, `Radius` в локальной СК вида, `Angle` в **радианах** через `DimensionAngles.ToRadians`, `AutoNominalValue=true`) → `Update()``Valid``((IDimensionText)dim).NominalValue` = диаметр (2·Radius). Размер «свободный» (без `BaseObject` — ассоциативная привязка отложена). Валидатор `DrawingValidation.RequirePositiveRadius`. `DimensionAngles.ToRadians` (с проверкой переполнения) добавлен в `DimensionOrientation.cs`; `RequirePositiveRadius` — в `DrawingValidation.cs`; инструмент в `DrawingTools.cs`. Итог: **76 инструментов, 218 тестов (141 unit + 77 integration).**
### ✅ Радиальный и угловой размеры на виде чертежа — реализованы (инкремент 5 вехи «2D-чертёж»)
**Решено.** Два новых инструмента завершают базовое семейство размеров:
`drawing_add_radial_dimension(xc,yc,radius, angle=0, viewNumber)` — радиальный размер (R). Паттерн API7: `RequireSymbols2DContainer(viewNumber)``RadialDimensions.Add()``IRadialDimension` (`Xc`, `Yc`, `Radius`, `Angle` в радианах, `DimensionType=true`, `AutoNominalValue=true`) → `Update()``Valid``NominalValue` = радиус. Переиспользует `RequirePositiveRadius` и `DimensionAngles.ToRadians`.
`drawing_add_angular_dimension(xc,yc, x1,y1, x2,y2, x3,y3, angleType="min", viewNumber)` — угловой размер. `angleType` = `"min"` (острый) | `"max"` (тупой) | `"more"` (рефлексный >180°). Паттерн API7: `RequireSymbols2DContainer(viewNumber)``AngleDimensions.Add(ksDrADimension)``IAngleDimension` (`Xc`,`Yc`,`X1`,`Y1`,`X2`,`Y2`,`X3`,`Y3`, `DimensionType=ksAngleDimTypeEnum`, `AutoNominalValue=true`) → `Update()``Valid``NominalValue` в градусах. Новый enum `AngleDimensionType {Min,Max,More}` + `AngleDimensionTypes.Parse/ToKompas` в `DimensionOrientation.cs` (+9 unit-тестов для маппинга).
Итог: **78 инструментов, 241 тест (150 unit + 91 integration).** Базовое семейство размеров завершено — виды, штамп, размеры (линейный, диаметральный, радиальный, угловой). Нереализовано: текстовые обозначения (шероховатость, допуски формы, выноски, тех. требования), ассоциативная привязка, рамка/формат.
### ✅ Текстовые обозначения на чертеже — реализованы (инкремент 6 вехи «2D-чертёж»)
**Решено.** Три новых инструмента завершают класс «2D-чертёж» на уровне текстовых аннотаций:
`drawing_add_rough(x,y, value?, signType, angle=0, viewNumber)` — знак шероховатости на виде. Паттерн API7: `ISymbols2DContainer.Roughs.Add()``IRough` (`BranchX0/Y0/Angle`) → `(IRoughParams)rough` (`SignType=ksRoughSignEnum`, `RoughParamText.Str = value`) → `Update()``Valid`. Новый enum `RoughSignType {NoProcessing, DeleteMaterial, WithoutDeleteMaterial}` + `RoughSignTypes.Parse/ToKompas` в `Drawings/RoughSignType.cs`. Возвращает `DrawingAnnotationResult { Value, ViewNumber }`.
`drawing_add_text(x,y, text, angle=0, viewNumber)` — свободная текстовая надпись на виде. Паттерн API7: `(IDrawingContainer)view.DrawingTexts.Add()``IDrawingText` (`X/Y/Angle`) → `(IText)dt.Str = text``Update()``Valid`. ВАЖНО: текст живёт в `IDrawingContainer`, а не `ISymbols2DContainer`; проверяется через `DrawingTexts.Count`. Возвращает `DrawingAnnotationResult { Value, ViewNumber }`.
`drawing_set_technical_requirements(text)` — технические требования документа (уровень документа, не вида). Паттерн API7: `(IDrawingDocument)doc.TechnicalDemand``td.Text.Str = text``td.Update()`. Перезаписывает прежние. Возвращает число строк.
Итог: **81 инструмент, 267 тестов (163 unit + 104 integration).** Нереализовано: ассоциативная привязка размеров и шероховатости к геометрии (`BaseObject`), рамка/формат листа, выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`).
### ✅ Ассоциативная привязка диаметрального/радиального к окружности — реализована (инкремент 7 вехи «2D-чертёж»)
**Решено (частично).** Параметр `associate=true` добавлен к `drawing_add_diametral_dimension` и `drawing_add_radial_dimension`. Реализация (API7, `DrawingService`): новый класс `CircularObjectMatch` — отбирает окружность-кандидата в `IDrawingContainer.Circles` по центру (xc,yc) + радиусу с допуском 1 мм; бросает при отсутствии или неоднозначности (концентрические окружности различаются по радиусу). Хелпер `RequireViewContainers` получает одновременно `ISymbols2DContainer` и `IDrawingContainer` для вида. `_Circle` реализует `IDrawingObject`, поэтому `dim.BaseObject = circle` без дополнительного приведения. `associate=false` — прежнее «свободное» поведение без изменений. Подтверждено спайком: `BaseObject=circle` без задания Xc/Yc/Radius → `Valid`, `NominalValue` из геометрии (цилиндр R10: Ø=20 / R=10). +5 unit-тестов (`CircularObjectMatchTests`) + 6 интеграционных (`DrawingAssociativeDimensionTests`). Итог: **81 инструмент, 278 тестов (168 unit + 110 integration).**
Нереализовано (следующие инкременты): привязка к дуге (`IDrawingContainer.Arcs`), ассоциативный угловой размер (`BaseObject1/2`, отрезки), ассоциативная шероховатость (`IRough.BaseObject`); рамка/основная надпись по ГОСТ-стилю (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов; выноски (`Leaders`), обозначения баз (`Bases`), допуски формы (`Tolerances`).
### ✅ Формат и ориентация листа чертежа — реализованы (инкремент 8 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_set_sheet_format(format, landscape, width?, height?, sheetNumber=1)` — задаёт формат и ориентацию активного листа чертежа. Паттерн API7: `doc2d.LayoutSheets.ItemByNumber[sheetNumber]``ILayoutSheet.Format` (`ISheetFormat`: `Format=ksDocumentFormatEnum`, `VerticalOrientation=!landscape`, `FormatWidth/Height` только для `format=user`) → `sheet.Update()`. Дефолтный лист нового чертежа — A4 книжный. Новые типы: enum `PaperFormat {A0,A1,A2,A3,A4,A5,User}` + `PaperFormats.Parse/ToKompas/FromKompas`; record `SheetFormatResult`; валидатор `DrawingValidation.ValidateFormatDimensions`; хелпер `RequireLayoutSheet`. Ключевая тонкость: для `format=user` КОМПАС выводит `VerticalOrientation` из соотношения сторон (`height>width`), флаг `landscape` в этом случае игнорируется — результат читается read-back из COM. Для стандартных форматов `width`/`height` запрещены (должны быть 0). +20 unit-тестов (`PaperFormats.Parse`, `ValidateFormatDimensions`) + 10 интеграционных (`DrawingSheetFormatTests`). Итог: **82 инструмента, 308 тестов (188 unit + 120 integration).**
Нереализовано: рамка по ГОСТ-стилю (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов.
### ✅ Линия-выноска с текстом — реализована (инкремент 9 вехи «2D-чертёж»)
**Решено.** Новый инструмент `drawing_add_leader(x,y, textX,textY, text, shelfDirection, viewNumber)` — ставит линию-выноску с надписью на полке на виде активного чертежа. Паттерн API7: `ISymbols2DContainer.Leaders.Add(ksDrLeader)``IBaseLeader``(IBranchs)bl.AddBranchByPoint(0,x,y)` (остриё, ответвление ответвлений — обязательно до `Update`, иначе `RPC_E_SERVERFAULT`) → `SetBranchTextPosition(textX,textY)``(ILeader)bl.TextOnShelf.Str = text` → опционально `ShelfDirection``Update()``Valid`. Новый enum `ShelfDirection {Auto,Right,Left,Up,Down}` + `ShelfDirections.Parse/ToKompas` в `src/Kompas.Mcp.Core/Drawings/ShelfDirection.cs`; переиспользует `DrawingAnnotationResult { Value, ViewNumber }`. +13 unit-тестов (`ShelfDirections.Parse`) + 10 интеграционных (`DrawingLeaderTests`, включая параметризацию направлений полки). Итог: **83 инструмента, 331 тест (201 unit + 130 integration).**
Нереализовано: обозначения баз (`Bases`), допуски формы (`Tolerances`); привязка к дуге (`Arcs`), ассоциативный угловой размер, ассоциативная шероховатость (`IRough.BaseObject`); рамка по ГОСТ-стилю; специальные выноски (позиция, клеймо, маркер).
### ⚠️ КОМПАС-3D v25 vs v24 — разведка для будущего апгрейда
**Контекст.** Проект таргетирует установленную локально КОМПАС-3D v24 Home (SDK в `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK`). `docs/Kompas3D_SDK/` (MD-база в репо) дистиллирована из справки v22 — новых интерфейсов v25 там нет и не появится без отдельной работы. Разведка проведена по официальным веб-источникам ASCON (v25 локально не установлена).
**Что нового в v25 (кратко, https://kompas.ru/kompas-3d/v25/, https://habr.com/ru/companies/ascon/news/1056484/):**
- Нативная версия для Linux (Альт 11.0, РЕД ОС 8.0, Astra Linux SE 1.8).
- 3D: «Создать вариант детализации» (упрощённые заменители в больших сборках), «Глубина проецирования» на ассоциативных видах, команда «Рельеф» (надписи/логотипы как выступ/гравировка), групповой выбор кривых/граней, несколько отверстий одной операцией.
- Поверхностное моделирование: эквидистанта вдоль нескольких граней, команда «Средняя линия», расширена «Поверхность скругления», сегментация полигональных объектов (реверс-инжиниринг: авто-разбиение сканов на плоскости/цилиндры/сферы/конусы/торусы).
- Импорт/экспорт: собственные конвертеры прямого чтения UGS/NX, ProE/Creo, SolidWorks, Inventor, CATIA V5, Solid Edge (плюс DXF/DWG).
- Чертежи/сборки: «Симметрия объектов», линейные размеры с выносными линиями касательными к окружностям, новые способы имитации движения компонентов сборки с контролем соударений.
- Новое приложение «Эргономика: Манекены»; доработки в специализированных приложениях (Композиты, Валы и передачи, Раскрой, Разъёмные соединения, строительная конфигурация — P&ID и др.) — вне scope MCP-сервера.
**Что нового в SDK / COM Automation API7 v25 (https://help.ascon.ru/KOMPAS_SDK/25/ru-RU/new_intrfs_v25.html, new_methods_v25.html) — потенциально релевантно для kompas3d-mcp.** Новые интерфейсы-кандидаты для будущих MCP-инструментов:
- `ICheckGeometry`/`ICheckGeometries`/`ICheckGeometryResult` — программная проверка корректности геометрии (кандидат для расширения `validate_part`).
- `ICollision`/`ICollisions`/`ICalculateCollisionsResult` — обнаружение пересечений/соударений объектов (новый класс проверок для сборок — сейчас в проекте отсутствует).
- `IFaceReplacer`/`IFaceReplacers`, `IFaceResizer`/`IFaceResizers` — замена/изменение размера грани (дополняет текущий `move_face`).
- `IMiddleLine`/`IMiddleLines` — поверхностная операция «средняя линия».
- `IArrayConstraint`, `ILinearArrayConstraint`, `ICircularArrayConstraint` — ограничения параметрических массивов объектов (2D/3D).
- `IDeformationManager`/`IDeformationObject` — деформация 3D-объектов.
- `IPart7.SaveModifiedPartAs` — сохранение изменённой вставки компонента сборки отдельным файлом.
- `ISpecificationExportParameters` / `ISpecification*.Export` — программный экспорт спецификаций.
- Точечные добавки: `IDrawingDocument.GetZoneByPoint/GetSheetNumberByPoint/SetCurrentModel`, `IDocuments.GetDocumentTypeByName`, `IStamp.IsCellPresent`, `IThread.GetLimitPoints`, `ITolerance`/`ITolerance3D` (доп. знаки допусков), `IApplicationDialogs` (ChoiceDocument/ChoiceTolerance/ReadPassword).
**Вывод / на проработку:** решения о переходе на v25 нет. Если оно будет принято — нужно либо доснять `docs/Kompas3D_SDK/` под v25, либо верифицировать эти интерфейсы напрямую рефлексией по новым interop-сборкам (тот же принцип «Haiku предлагает → Opus перепроверяет по DLL», что и сейчас). Приоритетные кандидаты: `ICheckGeometry` (валидация геометрии), `ICollision(s)` (проверка столкновений в сборке), `IFaceReplacer`/`IFaceResizer` (расширение прямого редактирования B-rep).
### ✅ Упаковка в плагин Claude Code — реализована
**Решено.** Сервер и оба навыка упакованы в плагин `kompas` (`plugin/`): манифест
`plugin/.claude-plugin/plugin.json`, лок версии сервера `plugin/server.lock.json`, лаунчер-доставщик
(`plugin/scripts/launch-kompas-mcp.ps1` + `KompasMcpBootstrap.psm1`), команда `/kompas:doctor`,
раскладка навыков junction'ами (`tools/sync-agent-assets.ps1`), CLI-флаг `--version`, CI/CD на Gitea
Actions (сборка+тесты на push/PR, релиз-пайплайн на тег). Подробности — `CLAUDE.md`
§«Claude Code plugin» и `docs/ARCHITECTURE.md` §12. Первого публичного релиза ещё нет — см. вопросы
ниже.
## ⚠️ Открытые вопросы плагина
- **Право на редистрибуцию interop-DLL АСКОН.** Self-contained ассет релиза включает вендорские
`libs/kompas-interop/*.dll`. Блокирует первый публичный релиз — обсуждается отдельно с АСКОН/юристами.
- **Junction'ы ломают переключение веток.** `git checkout` на коммит без каталога `plugin/skills`
падает: ссылки `.claude/skills/<name>`/`.agents/skills/<name>` повисают, чекаут обрывается
посередине. Обход — снять junction'ы (`tools/sync-agent-assets.ps1 -Remove`), переключиться,
разложить заново (см. порядок действий в `CLAUDE.md`).
- **Мусор в `%LOCALAPPDATA%\kompas-mcp\.tmp`** при аварийном завершении процесса-установщика никем
не чистится (кроме удаления записей старше порога при следующей успешной установке —
`Remove-KompasMcpStaleTemp` вызывается только изнутри `Install-KompasMcpServer`, а не отдельно).
- **`%LOCALAPPDATA%\kompas-mcp` не очищается** ни при обновлении плагина (старые версии копятся),
ни при его удалении.
- **Формулировки «релиза ещё нет»** продублированы в трёх местах (`plugin/README.md` и обоих
`plugin/adapters/*/README.md`) — после первого релиза все три станут ложными и требуют синхронной правки.
-76
View File
@@ -1,76 +0,0 @@
# TODO проекта kompas3d-mcp
Канонический бэклог **оставшихся** этапов. Что уже сделано — см. `CLAUDE.md` → «Current state» и
`docs/ARCHITECTURE.md` §10 (дорожная карта). Метод работы над этапом — `docs/superpowers/NEXT-SESSION.md`
(спайк → спек → ревью Codex → TDD → ревью реализации → доки → merge/push).
Статус: ☐ не начато · ▣ частично · ⛔ заблокировано (недостижимо текущим COM API) · ✓ сделано
---
## Веха 2D-ЧЕРТЁЖ — текущий фокус (самый ценный растущий класс)
Готово: стандартные виды, основная надпись (штамп), размеры (линейный/диаметральный/радиальный/угловой),
текстовые обозначения (шероховатость, свободный текст, технические требования).
-**Ассоциативная привязка размеров и шероховатости к геометрии вида** (`BaseObject`/`BaseObject1/2`).
Обозначение «прилипает» к геометрии вида и обновляется с моделью, а не задаётся координатами.
- ✓ Диаметральный/радиальный → **окружность** (`Circles`): флаг `associate` (инкремент 7); поиск по
центру+радиусу (`CircularObjectMatch`), значение Ø/R из геометрии.
- ☐ Привязка к **дуге** (`Arcs`) — нужен свой спайк (`IArc` как `BaseObject`; дуги одного родителя
неотличимы по центру+радиусу — нужен ключ по углу/точке).
-**Угловой** → пара отрезков (`BaseObject1/2`, `LineSegments`).
-**Шероховатость**`IRough.BaseObject` (привязка к контуру/ребру).
- ⛔/☐ **Линейный**у `ILineDimension` нет `BaseObject`; нужен иной механизм (отложено).
-**Формат/ориентация листа** (A0A5/user, книжная/альбомная) — `drawing_set_sheet_format`
(инкремент 8); `ILayoutSheet.Format`/`ISheetFormat`. Остаётся: ☐ рамка/основная надпись по
конкретному ГОСТ-стилю оформления (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов.
-**Выноски** (`Leaders`) — `drawing_add_leader` (инкремент 9): остриё + полка с текстом,
`shelfDirection`. Остаётся: ☐ многострочный/под-полкой текст (`TextUnderShelf`), несколько
ответвлений, привязка к геометрии (`IBranchs.SetBaseObject`), спец. выноски (позиция/клеймо/маркер).
-**Обозначения баз** (`Bases`) — `ISymbols2DContainer.Bases`.
-**Допуски формы и расположения** (`Tolerances`) — `ISymbols2DContainer.Tolerances` (обычно в связке с базой).
- ☐ (опц.) **Доп. виды**: разрез/сечение (`CutLines`), выносной элемент (`RemoteElements`), местный вид.
- ☐ (опц.) **Осевые линии / обозначения центра** (`AxisLines`, `CentreMarkers`).
## Класс СБОРКИ
Готово: вставка компонента (`assembly_add_component`), сопряжения `coincidence`/`distance`.
-**Доп. типы сопряжений**: `parallel`, `perpendicular`, `concentric`, `angle`, `tangency`
(`MateConstraintType`; механика `assembly_add_mate` готова — расширить enum `MateType` + валидаторы).
-**Авто-позиционирование** компонентов (решатель сопряжений по нескольким связям).
-**Спецификация** (`ISpecification`) — таблица состава изделия.
## Класс ДЕТАЛЬ / параметрика / геометрия
Готово: насыщен (эскизы, формообразующие, массивы/зеркало, отверстия, уклон, переменные, STEP,
прямое редактирование B-rep, структурный осмотр) — см. `ARCHITECTURE.md` §10.
-**Параметрические эскизы** (размер эскиза управляется переменной) — недостижимо через COM API
(`ksCDimWithVariable` не конструируется из внешней автоматизации). См.
`docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`. **Не повторять** без новой информации.
-**Пакет E (вспомогательная геометрия, продолжение)**: ось, точка, плоскость по 3 точкам / по углу
(`sketch_create_on_offset_plane` уже есть).
-**Рассечение/перемещение тела как инструменты** (`SplitSolids`/`BodyRepositions`) — механика есть
(`split_solid_by_plane`, `move_body` частично), полный workflow split-reposition не продуктизирован.
-**Свойства документа** (`IPropertyMng`/`IPropertyKeeper`) — чтение/запись пользовательских свойств.
## Сквозное / инфраструктура / технический долг
-**Утечки транзитных RCW** (долг v2-2): `EntityCollection`/`GetTopPart`/QI-контейнеры/`IRoughParams`
и пр. не освобождаются → накопление за длинную сессию. Нужна единая стратегия (scope/`ReleaseComObject`),
не точечно — поэтому отложено как самостоятельный этап.
-**Насос сообщений / отмена**: `CancellationToken` не прерывает идущий COM-вызов; модальный диалог
КОМПАС блокирует всю очередь STA-потока.
-**Транспорт HTTP/SSE** — опция для удалённых клиентов (сейчас только stdio).
## Навык / методика
- ☐ Промотировать проверенные приёмы 2D-чертежа из `usecases/` в навык `.claude/skills/kompas-3d/`
(playbook «оформление чертежа»: виды → размеры → обозначения → штамп → тех. требования).
---
*Обновлять по завершении каждого инкремента (отмечать ✓, добавлять вскрытые подэтапы). Источник
приоритетов для следующей сессии — верх раздела «2D-ЧЕРТЁЖ».*
-388
View File
@@ -1,388 +0,0 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>КОМПАС-3D MCP — управление CAD через LLM</title>
<style>
:root{
--bg:#0b1020; --bg2:#121a33; --card:#161f3d; --card2:#1b264a;
--txt:#e8edf7; --muted:#9aa7c7; --line:#26335c;
--accent:#4da3ff; --accent2:#7c5cff; --green:#36d399; --amber:#fbbd23;
--grad:linear-gradient(135deg,#4da3ff 0%,#7c5cff 100%);
}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{
font-family:"Segoe UI",system-ui,-apple-system,Roboto,Arial,sans-serif;
background:radial-gradient(1200px 600px at 80% -10%,#1c2750 0%,var(--bg) 55%) no-repeat,var(--bg);
color:var(--txt);line-height:1.6;-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1120px;margin:0 auto;padding:0 24px}
section{padding:64px 0}
h2{font-size:30px;margin-bottom:8px;letter-spacing:-.5px}
.sub{color:var(--muted);margin-bottom:36px;max-width:640px}
.eyebrow{color:var(--accent);font-weight:700;font-size:13px;letter-spacing:2px;text-transform:uppercase}
/* HERO */
.hero{padding:96px 0 72px;position:relative;overflow:hidden}
.hero::after{content:"";position:absolute;inset:0;background:
radial-gradient(600px 300px at 15% 20%,rgba(124,92,255,.18),transparent 60%);
pointer-events:none}
.badge{display:inline-flex;align-items:center;gap:8px;background:rgba(77,163,255,.1);
border:1px solid var(--line);color:var(--accent);padding:6px 14px;border-radius:999px;
font-size:13px;font-weight:600;margin-bottom:22px}
.dot{width:8px;height:8px;border-radius:50%;background:var(--amber);box-shadow:0 0 10px var(--amber)}
.hero h1{font-size:54px;line-height:1.05;letter-spacing:-1.5px;margin-bottom:18px;font-weight:800}
.hero h1 .g{background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.hero p.lead{font-size:19px;color:var(--muted);max-width:680px;margin-bottom:30px}
.chips{display:flex;flex-wrap:wrap;gap:10px}
.chip{background:var(--card);border:1px solid var(--line);border-radius:10px;
padding:8px 14px;font-size:13px;font-weight:600;color:#cdd7f2}
.chip b{color:var(--accent)}
/* GRID CARDS */
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
.card{background:linear-gradient(180deg,var(--card),var(--card2));border:1px solid var(--line);
border-radius:16px;padding:24px;transition:transform .2s,border-color .2s}
.card:hover{transform:translateY(-4px);border-color:var(--accent)}
.card .ic{width:44px;height:44px;border-radius:12px;background:var(--grad);
display:flex;align-items:center;justify-content:center;font-size:22px;margin-bottom:14px}
.card h3{font-size:17px;margin-bottom:6px}
.card p{color:var(--muted);font-size:14px}
.card code{background:rgba(255,255,255,.06);padding:1px 6px;border-radius:5px;font-size:12px;color:#cdd7f2}
/* WORKFLOW */
.flow{display:flex;align-items:stretch;gap:0;flex-wrap:wrap;margin-top:8px}
.step{flex:1;min-width:150px;background:var(--card);border:1px solid var(--line);
border-radius:14px;padding:18px 16px;position:relative;text-align:center}
.step .n{font-size:12px;color:var(--accent);font-weight:700}
.step h4{font-size:15px;margin:6px 0 4px}
.step p{font-size:12.5px;color:var(--muted)}
.arrow{display:flex;align-items:center;justify-content:center;color:var(--accent2);
font-size:24px;padding:0 8px}
.loopnote{margin-top:14px;color:var(--muted);font-size:14px;text-align:center}
.loopnote b{color:var(--green)}
/* STACK */
.stack{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.scard{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px;text-align:center}
.scard .k{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:1px}
.scard .v{font-size:18px;font-weight:700;margin-top:6px;color:#dfe7fb}
.scard .v small{display:block;font-size:12px;color:var(--muted);font-weight:500;margin-top:3px}
/* TODO */
.todo-grid{display:grid;grid-template-columns:1fr 1fr;gap:24px}
.panel{background:linear-gradient(180deg,var(--card),var(--card2));border:1px solid var(--line);
border-radius:16px;padding:26px}
.panel h3{font-size:18px;margin-bottom:4px;display:flex;align-items:center;gap:10px}
.tag{font-size:11px;font-weight:700;padding:3px 9px;border-radius:999px;letter-spacing:.5px}
.tag.done{background:rgba(54,211,153,.15);color:var(--green);border:1px solid rgba(54,211,153,.3)}
.tag.now{background:rgba(251,189,35,.15);color:var(--amber);border:1px solid rgba(251,189,35,.3)}
.tag.next{background:rgba(124,92,255,.15);color:#b9a6ff;border:1px solid rgba(124,92,255,.3)}
.panel .meta{color:var(--muted);font-size:13px;margin-bottom:16px}
ul.list{list-style:none}
ul.list li{display:flex;gap:11px;padding:8px 0;border-bottom:1px dashed var(--line);font-size:14.5px}
ul.list li:last-child{border-bottom:0}
.mark{flex:0 0 20px;width:20px;height:20px;border-radius:6px;display:flex;align-items:center;
justify-content:center;font-size:12px;font-weight:700;margin-top:2px}
.mark.ok{background:var(--green);color:#04231a}
.mark.todo{background:transparent;border:1.5px solid var(--line)}
.mark.wip{background:var(--amber);color:#3a2c00}
li.muted span.t{color:var(--muted)}
/* progress */
.prog{margin:18px 0 4px}
.bar{height:10px;border-radius:999px;background:#0e1530;overflow:hidden;border:1px solid var(--line)}
.bar > i{display:block;height:100%;background:var(--grad);border-radius:999px}
.progrow{display:flex;justify-content:space-between;font-size:13px;color:var(--muted);margin-bottom:8px}
/* roadmap timeline */
.timeline{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:14px;margin-top:8px}
.tl{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px 16px;position:relative}
.tl .ph{font-size:12px;font-weight:700;color:var(--accent)}
.tl h4{font-size:15px;margin:8px 0 6px}
.tl p{font-size:12.5px;color:var(--muted)}
.tl.cur{border-color:var(--amber);box-shadow:0 0 0 1px rgba(251,189,35,.25)}
footer{border-top:1px solid var(--line);padding:34px 0;color:var(--muted);font-size:14px}
footer a{color:var(--accent);text-decoration:none}
footer a:hover{text-decoration:underline}
.links{display:flex;gap:20px;flex-wrap:wrap;margin-top:10px}
@media(max-width:1020px){
.timeline{grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}
}
@media(max-width:880px){
.grid,.stack{grid-template-columns:1fr 1fr}
.todo-grid{grid-template-columns:1fr}
.timeline{grid-template-columns:1fr 1fr}
.hero h1{font-size:40px}
.arrow{transform:rotate(90deg)}
}
@media(max-width:520px){.grid,.stack,.timeline{grid-template-columns:1fr}}
</style>
</head>
<body>
<!-- HERO -->
<header class="hero">
<div class="wrap">
<span class="badge"><span class="dot" style="background:var(--green);box-shadow:0 0 10px var(--green)"></span> Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension+drawing_add_diametral_dimension+drawing_add_radial_dimension+drawing_add_angular_dimension+drawing_add_rough+drawing_add_text+drawing_set_technical_requirements+associate+drawing_set_sheet_format+drawing_add_leader+set_variable_note+плагин Claude Code «kompas» (84 инструмента)</span>
<h1>КОМПАС-3D <span class="g">MCP-сервер</span><br>управление CAD языком LLM</h1>
<p class="lead">MCP-сервер, который превращает операции КОМПАС-3D — создание документов,
эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом —
COM Automation API АСКОН.</p>
<div class="chips">
<span class="chip"><b>.NET 8</b> · C#</span>
<span class="chip">транспорт <b>stdio</b></span>
<span class="chip"><b>COM</b> Automation · API7 / API5</span>
<span class="chip">Windows <b>x64</b></span>
<span class="chip">КОМПАС-3D <b>v24 Home</b></span>
<span class="chip">👁 <b>зрение агента</b></span>
</div>
</div>
</header>
<!-- KEY FEATURES -->
<section id="features">
<div class="wrap">
<div class="eyebrow">Возможности</div>
<h2>Что умеет MCP-сервер</h2>
<p class="sub">Инструменты сгруппированы вокруг живого инженерного цикла моделирования —
от подключения к КОМПАС до построения и опроса детали.</p>
<div class="grid">
<div class="card">
<div class="ic">🔌</div>
<h3>Подключение к КОМПАС</h3>
<p>Attach к запущенному или launch нового экземпляра по ProgID, переход на API7
через <code>ksGetApplication7()</code>, управление видимостью окна.</p>
</div>
<div class="card">
<div class="ic">📄</div>
<h3>Документы</h3>
<p>Создание, открытие, сохранение и закрытие деталей, сборок, чертежей и фрагментов
через <code>IDocuments</code>.</p>
</div>
<div class="card">
<div class="ic">✏️</div>
<h3>Эскизы</h3>
<p>Эскиз на плоскости или грани, примитивы (линия, окружность, дуга,
прямоугольник), безопасный цикл <code>BeginEdit → EndEdit</code>.</p>
</div>
<div class="card">
<div class="ic">🧊</div>
<h3>3D-операции</h3>
<p>Выдавливание и вырез, вращение, перестроение. Управление направлением, глубиной и
типом окончания (<code>ksEndTypeEnum</code>).</p>
</div>
<div class="card">
<div class="ic">🔍</div>
<h3>Структурный осмотр</h3>
<p><code>describe_model</code> — паспорт детали (МЦХ, тела, дерево, переменные) одним вызовом.
<code>describe_face</code> · <code>describe_edge</code> · <code>measure</code>.
Предпочтительнее снимка — без расхода токенов изображения.</p>
</div>
<div class="card">
<div class="ic">🎯</div>
<h3>Выборка и запросы</h3>
<p>Перечисление граней, рёбер и компонентов сборки с классификацией; МЦХ, габарит.
<code>list_faces</code> · <code>list_edges</code> · <code>list_components</code>.</p>
</div>
<div class="card">
<div class="ic">📦</div>
<h3>STEP импорт / экспорт</h3>
<p>Импорт .step/.stp в новый документ (деталь или сборка, создание файлов компонентов);
экспорт в AP203/AP214/AP242 через встроенный конвертер КОМПАС.</p>
</div>
<div class="card">
<div class="ic">✂️</div>
<h3>Прямое редактирование</h3>
<p><code>move_face</code>: сдвинуть грань на N мм вдоль нормали (наружу или внутрь).
Работает на импортированной B-rep через <code>FaceMover</code> API7.</p>
</div>
<div class="card">
<div class="ic">👁️</div>
<h3>Зрение агента</h3>
<p>Рендер модели в PNG через временный файл и возврат как image-контент MCP — агент <b>видит</b>
результат. Fallback после <code>describe_model</code> для визуально-пространственных вопросов.</p>
</div>
</div>
</div>
</section>
<!-- WORKFLOW -->
<section id="workflow" style="background:linear-gradient(180deg,transparent,rgba(124,92,255,.05))">
<div class="wrap">
<div class="eyebrow">Ядро сценария</div>
<h2>Цикл «эскиз → операция → эскиз»</h2>
<p class="sub">Главный пользовательский поток v1 — итеративное параметрическое моделирование
с визуальной проверкой: рисуем профиль, превращаем в объём, <b>смотрим на результат</b>,
выбираем грань, рисуем следующий профиль.</p>
<div class="flow">
<div class="step"><div class="n">01</div><h4>Документ</h4><p>создать деталь (part)</p></div>
<div class="arrow"></div>
<div class="step"><div class="n">02</div><h4>Эскиз</h4><p>на плоскости XOY, профиль</p></div>
<div class="arrow"></div>
<div class="step"><div class="n">03</div><h4>Выдавить</h4><p>boss-операция → тело</p></div>
<div class="arrow"></div>
<div class="step" style="border-color:var(--accent2)"><div class="n" style="color:var(--accent2)">04 👁</div><h4>Снимок</h4><p>агент видит результат</p></div>
<div class="arrow"></div>
<div class="step"><div class="n">05</div><h4>Выбрать грань</h4><p>по точке на теле</p></div>
<div class="arrow"></div>
<div class="step"><div class="n">06</div><h4>Вырез</h4><p>новый эскиз → cut</p></div>
</div>
<p class="loopnote"><b>Повторять со зрением</b> — снимок → новая грань → новый эскиз → новая операция → снова снимок, пока деталь не готова</p>
</div>
</section>
<!-- STACK -->
<section id="stack">
<div class="wrap">
<div class="eyebrow">Технологии</div>
<h2>Стек решения</h2>
<p class="sub">Выбор обоснован первоклассным COM-интеропом .NET и официальным C# MCP SDK —
подробности в документе архитектуры.</p>
<div class="stack">
<div class="scard"><div class="k">Платформа</div><div class="v">.NET 8<small>C# · win-x64</small></div></div>
<div class="scard"><div class="k">MCP SDK</div><div class="v">ModelContextProtocol<small>транспорт stdio</small></div></div>
<div class="scard"><div class="k">Интероп</div><div class="v">COM Automation<small>API7 + API5 (.tlb)</small></div></div>
<div class="scard"><div class="k">Потоки</div><div class="v">STA-поток<small>насос сообщений</small></div></div>
</div>
</div>
</section>
<!-- TODO -->
<section id="todo" style="background:linear-gradient(180deg,transparent,rgba(77,163,255,.05))">
<div class="wrap">
<div class="eyebrow">Прогресс</div>
<h2>TODO проекта</h2>
<p class="sub">Честный срез состояния: фундамент подготовки готов, реализация сервера —
впереди.</p>
<div class="prog">
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · hole_counterbore · hole_countersink · hole_conic · draft (API5) · assembly_add_component · assembly_add_mate · drawing_create_standard_views · drawing_fill_title_block · drawing_add_linear_dimension · drawing_add_diametral_dimension · drawing_add_radial_dimension · drawing_add_angular_dimension · drawing_add_rough · drawing_add_text · drawing_set_technical_requirements · associate (ассоциативная привязка диам./радиального) · drawing_set_sheet_format (формат листа) · drawing_add_leader (выноска) · set_variable_note · плагин Claude Code «kompas» + CI/CD на Gitea Actions · навыки kompas-3d/kompas-fdm-design · 84 инструмента · 357 .NET-тестов + 41 Pester</span></div>
<div class="bar"><i style="width:97%"></i></div>
</div>
<div class="todo-grid" style="margin-top:26px">
<!-- DONE -->
<div class="panel">
<h3>Подготовка <span class="tag done">ГОТОВО</span></h3>
<p class="meta">Исследование, документация и инструменты разработчика</p>
<ul class="list">
<li class="muted"><span class="mark ok"></span><span class="t">Исследование SDK и COM API (API5/API7, паттерны подключения)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">CLAUDE.md — гайд по репозиторию и API</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Навигатор по справке: <code>kdoc.py</code> (навык kompas-sdk-research) + индекс на 26k стр.</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Документ архитектуры <code>docs/ARCHITECTURE.md</code></span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Выбор стека: .NET/C#, stdio, цикл «эскиз↔операция»</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Решение о визуальной обратной связи (зрение агента, рендер в растр)</span></li>
</ul>
</div>
<!-- NOW -->
<div class="panel">
<h3>Фундамент v1 <span class="tag done">ГОТОВО</span></h3>
<p class="meta">Скелет сервера и связь с КОМПАС</p>
<ul class="list">
<li class="muted"><span class="mark ok"></span><span class="t">Спайки S1–S4: interop, подключение, STA, рендер</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Решение Core/Host/Tests, Directory.Build.props, git</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Маппинг перечислений (enum ↔ строка)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">STA-диспетчер (выделенный поток, очередь задач)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">MCP-хост на stdio, логи в stderr</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Подключение по ProgID (attach/launch)</span></li>
</ul>
</div>
<!-- TOOLS -->
<div class="panel">
<h3>Инструменты v3+ <span class="tag done">ГОТОВО</span></h3>
<p class="meta">84 инструмента, отдаются по MCP-протоколу</p>
<ul class="list">
<li class="muted"><span class="mark ok"></span><span class="t"><code>System</code>: connect · status · set_visible</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Documents</code>: create · open · save · save_as · close · active</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Sketch</code>: create · on_face (точка/индекс) · <b>on_offset_plane</b> (смещённая плоскость) · add line/circle/arc/arc_3points/rectangle/axis/ellipse/polyline/polygon/spline/point · close</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Features</code>: extrude · revolve · fillet_edge/chamfer_edge (точка + индекс) · <b>shell</b> (оболочка) · <b>rib</b> (ребро жёсткости) · <b>sweep</b> (кинематическая) · <b>loft</b> (по сечениям) · <b>linear_pattern</b> · <b>circular_pattern</b> · <b>mirror_operation</b> · <b>mirror_body</b> · <b>hole</b> (API7) · <b>hole_counterbore</b> (цековка, API7) · <b>hole_countersink</b> (зенковка, API7) · <b>hole_conic</b> (коническое, API7) · <b>draft</b> (уклон, API5) · rebuild</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Edit</code>: move_face · split_solid_by_plane · move_body · boolean_union</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Inspection</code>: describe_model · list_features · list_bodies · list_variables · describe_face · describe_edge · measure</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Vision</code>: model_snapshot 👁️ (fallback)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Query</code>: get_part_info · get_bounding_box · list_faces · list_edges · list_components</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Variables</code>: <b>create_variable</b> · <b>set_variable</b> · <b>set_variable_note</b> · <b>delete_variable</b> (пакет D)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Conversion</code>: import_step · export_step</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Assembly</code>: <b>assembly_add_component</b> (вставка компонента .m3d/.a3d в сборку, API7) · <b>assembly_add_mate</b> (сопряжения coincidence/distance, API7)</span></li>
<li class="muted"><span class="mark ok"></span><span class="t"><code>Drawing</code>: <b>drawing_create_standard_views</b> (стандартные ассоциативные виды чертежа, API7) · <b>drawing_fill_title_block</b> (основная надпись/штамп: обозначение, наименование, материал, API7) · <b>drawing_add_linear_dimension</b> (линейный размер на виде, ISymbols2DContainer, API7) · <b>drawing_add_diametral_dimension</b> (диаметральный размер Ø, IDiametralDimension, <b>associate=true</b> для ассоциативной привязки к окружности, API7) · <b>drawing_add_radial_dimension</b> (радиальный размер R, IRadialDimension, <b>associate=true</b> аналогично, API7) · <b>drawing_add_angular_dimension</b> (угловой размер, IAngleDimension, angleType=min/max/more, API7) · <b>drawing_add_rough</b> (знак шероховатости, IRough/IRoughParams, signType=delete/without/none, API7) · <b>drawing_add_text</b> (свободный текст на виде, IDrawingContainer/IDrawingText, API7) · <b>drawing_set_technical_requirements</b> (тех. требования документа, IDrawingDocument/TechnicalDemand, API7) · <b>drawing_set_sheet_format</b> (формат и ориентация листа, ILayoutSheet/ISheetFormat, PaperFormat A0A5/user, API7) · <b>drawing_add_leader</b> (линия-выноска с текстом, ISymbols2DContainer/IBaseLeader/IBranchs/ILeader, ShelfDirection enum, API7)</span></li>
<li><span class="mark wip"></span><span>2D-чертёж: рамка/основная надпись по ГОСТ-стилю (LayoutLibraryFileName/LayoutStyleNumber), несколько листов · привязка к дуге (Arcs), ассоциативный угловой размер, ассоциативная шероховатость (IRough.BaseObject) · обозначения баз (Bases), допуски формы (Tolerances), специальные выноски (позиция/клеймо/маркер) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — <b>исследовано, недоступно через COM API</b> · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)</span></li>
</ul>
</div>
<!-- QUALITY -->
<div class="panel">
<h3>Качество и поставка <span class="tag now">В РАБОТЕ</span></h3>
<p class="meta">Тесты, сборка, плагин Claude Code, CI/CD</p>
<ul class="list">
<li class="muted"><span class="mark ok"></span><span class="t">Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Итого 357 .NET-тестов зелёных (226 unit + 131 integration) + 41 Pester-тест плагина</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Сборка <code>kompas-mcp.exe</code>, README с конфигом клиента, CLI-флаг <code>--version</code></span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Навыки <code>kompas-3d</code> · <code>kompas-fdm-design</code> (методика) + полигон <code>usecases/</code></span></li>
<li class="muted"><span class="mark ok"></span><span class="t">Плагин Claude Code <code>kompas</code>: манифест, лаунчер-доставщик, <code>server.lock.json</code>, <code>/kompas:doctor</code>, навыки junction'ами</span></li>
<li class="muted"><span class="mark ok"></span><span class="t">CI на Gitea Actions: сборка+тесты на push/PR, релиз-пайплайн self-contained publish на тег → ветка <code>dist</code></span></li>
<li><span class="mark todo"></span><span>Первый публичный релиз, запись плагина в каталоге <code>home-repo-cc</code>, право на редистрибуцию interop-DLL АСКОН</span></li>
</ul>
</div>
</div>
</div>
</section>
<!-- ROADMAP -->
<section id="roadmap">
<div class="wrap">
<div class="eyebrow">Дорожная карта</div>
<h2>После v1</h2>
<p class="sub">Наращиваем покрытие API волнами — от простых операций к параметрике,
черчению и сборкам.</p>
<div class="timeline">
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">v1 · готово ✓</div><h4>Цикл моделирования</h4><p>Документы, эскизы, выдавливание/вырез, снимок модели</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">v2 · готово ✓</div><h4>Операции, выборка, STEP</h4><p>Эскиз на грани · вращение · скругление/фаска · list_faces/edges · import_step · export_step · list_components · навык kompas-3d</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">v3 · готово ✓</div><h4>B-rep + структурный осмотр</h4><p><b>move_face</b> (сдвиг грани на N мм) · <b>describe_model</b> · list_features · list_bodies · list_variables · describe_face · describe_edge · measure</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет A · готово ✓</div><h4>Богатые эскизы</h4><p>arc · arc_3points · ellipse · polyline · polygon · spline · point (+7 инструментов); <b>PartModeler</b> разбит на partial-классы; <b>SketchGeometry</b></p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет B · готово ✓</div><h4>Формообразующие операции</h4><p><b>shell</b> ✓ · <b>rib</b> ✓ · <b>sweep</b> ✓ · <b>loft</b> ✓ (по сечениям) · <b>offset-plane</b> ✓ (пакет E)</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет C · готово ✓</div><h4>Массивы и зеркало</h4><p><b>linear_pattern</b> ✓ · <b>circular_pattern</b> ✓ · <b>mirror_operation</b> ✓ · <b>mirror_body</b> ✓ · <b>hole</b> ✓ (API7)</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">draft · готово ✓</div><h4>Уклон</h4><p><b>draft</b> ✓ (API5 <code>ksInclineDefinition</code>, o3d_incline=42) · 64 инструмента · 115 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">пакет D start · готово ✓</div><h4>Переменные модели</h4><p><b>create_variable</b> · <b>set_variable</b> · <b>delete_variable</b> (API5 VariableService) · list_variables исправлен · 67 инструментов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">отверстия под крепёж · готово ✓</div><h4>Цековка, зенковка и конус</h4><p><b>hole_counterbore</b> (ksHTCounterbore) · <b>hole_countersink</b> (ksHTCountersinking) · <b>hole_conic</b> (ksHTConic, IConicHoleParameters) · HoleCore рефакторен (configure-колбэк) · 70 инструментов · 119 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">сборки start · готово ✓</div><h4>Вставка компонентов</h4><p><b>assembly_add_component</b> (API7 IParts7.AddFromFile + IPlacement3D)</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">сборки инкремент 2 · готово ✓</div><h4>Сопряжения</h4><p><b>assembly_add_mate</b> (coincidence/distance, API7 IMateConstraints3D) · IntegrationTestBase · 72 инструмента · 156 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 2 ✓</div><h4>Стандартные виды + основная надпись</h4><p><b>drawing_create_standard_views</b> (AddStandartViews, API7) · <b>drawing_fill_title_block</b> (IStamp.Text[id].Str, API7) · 74 инструмента · 188 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 3 ✓</div><h4>Линейные размеры</h4><p><b>drawing_add_linear_dimension</b> (ISymbols2DContainer/LineDimensions.Add/ILineDimension, локальная СК вида, API7) · ViewNumbers в результате видов · 75 инструментов · 203 теста</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 4 ✓</div><h4>Диаметральный размер</h4><p><b>drawing_add_diametral_dimension</b> (ISymbols2DContainer.DiametralDimensions.Add/IDiametralDimension, Xc/Yc/Radius/Angle рад, API7) · RequireSymbols2DContainer общий хелпер · DimensionAngles.ToRadians · 76 инструментов · 218 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 5 ✓</div><h4>Радиальный и угловой размеры</h4><p><b>drawing_add_radial_dimension</b> (IRadialDimension, DimensionType=true, API7) · <b>drawing_add_angular_dimension</b> (IAngleDimension, angleType=min/max/more, AngleDimensionType enum, API7) · базовое семейство размеров завершено · 78 инструментов · 241 тест</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 6 ✓</div><h4>Текстовые обозначения</h4><p><b>drawing_add_rough</b> (IRough/IRoughParams, signType=delete/without/none, API7) · <b>drawing_add_text</b> (IDrawingContainer/IDrawingText/IText, API7) · <b>drawing_set_technical_requirements</b> (IDrawingDocument/TechnicalDemand, уровень документа, API7) · DrawingAnnotationResult · RoughSignType enum · 81 инструмент · 267 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 7 ✓</div><h4>Ассоциативная привязка к окружности</h4><p><b>associate=true</b> у drawing_add_diametral_dimension и drawing_add_radial_dimension · CircularObjectMatch (поиск по центру+радиусу, допуск 1 мм) · RequireViewContainers · dim.BaseObject=circle · значение из геометрии · 81 инструмент · 278 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 8 ✓</div><h4>Формат и ориентация листа</h4><p><b>drawing_set_sheet_format</b> (ILayoutSheet.Format / ISheetFormat, ksDocumentFormatEnum, A0A5/user, VerticalOrientation) · PaperFormat enum · SheetFormatResult · ValidateFormatDimensions · 82 инструмента · 308 тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">2D-чертёж · инкремент 9 ✓</div><h4>Линия-выноска</h4><p><b>drawing_add_leader</b> (ISymbols2DContainer.Leaders.Add / IBaseLeader / IBranchs.AddBranchByPoint / ILeader.TextOnShelf, ShelfDirection enum auto/right/left/up/down, API7) · DrawingAnnotationResult · 83 инструмента · 331 тест</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">переменные · готово ✓</div><h4>Примечание переменной</h4><p><b>set_variable_note</b> (пакет D) · 84 инструмента · 357 .NET-тестов</p></div>
<div class="tl" style="border-color:var(--green)"><div class="ph" style="color:var(--green)">плагин · готово ✓</div><h4>Плагин Claude Code</h4><p>Плагин <b>kompas</b> (<code>plugin/</code>): манифест · <code>server.lock.json</code> · лаунчер-доставщик (KompasMcpBootstrap.psm1) · <b>/kompas:doctor</b> · навыки junction'ами · CLI <code>--version</code> · CI/CD на Gitea Actions · канал дистрибуции — ветка <code>dist</code>. Первого релиза пока нет.</p></div>
<div class="tl cur"><div class="ph">далее · в работе ▶</div><h4>Оформление чертежа</h4><p>Рамка/надпись по ГОСТ-стилю (LayoutLibraryFileName) · несколько листов · привязка к дуге (Arcs) · ассоциативный угловой размер · ассоциативная шероховатость (IRough.BaseObject) · обозначения баз (Bases) · допуски формы (Tolerances) · специальные выноски (позиция/клеймо/маркер) · пакет E (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API · первый публичный релиз плагина</p></div>
</div>
</div>
</section>
<footer>
<div class="wrap">
<div>КОМПАС-3D MCP · документация проекта</div>
<div class="links">
<a href="../README.md">📦 README</a>
<a href="../plugin/README.md">🧩 Плагин Claude Code</a>
<a href="ARCHITECTURE.md">📐 Документ архитектуры</a>
<a href="IMPLEMENTATION_PLAN.md">🛠️ План реализации</a>
<a href="OPEN_QUESTIONS.md">❓ Открытые вопросы</a>
<a href="../CLAUDE.md">📘 CLAUDE.md</a>
<a href="KOMPAS_SDK_ru-RU/index.html">📚 Справка SDK КОМПАС-3D</a>
<a href="https://help.ascon.ru/KOMPAS_SDK/22/ru-RU/index.html">🌐 Онлайн-справка АСКОН</a>
</div>
</div>
</footer>
</body>
</html>
-54
View File
@@ -1,54 +0,0 @@
# Промт для следующей сессии
Скопируй блок ниже как стартовое сообщение новой сессии Claude Code.
---
Продолжаем разработку MCP-сервера КОМПАС-3D (**kompas3d-mcp**). Работай автономно: коммить и
пушь в `main`, каждую фичу (спек и реализацию) прогоняй через ревью Codex. Для интеграционных
тестов **КОМПАС-3D должен быть запущен**.
**Оставшиеся этапы — канонический бэклог: [`docs/TODO.md`](TODO.md)** (по классам + сквозное; верх раздела
«2D-ЧЕРТЁЖ» = следующий приоритет). Обновляй его по завершении каждого инкремента.
**Сначала сориентируйся** — этот промт задаёт направление, а не заменяет источники:
- `CLAUDE.md` — текущее состояние, проверенные факты реализации и конвенции (STA-поток, COM-QI
контейнеры, отложенный долг по RCW, команды сборки/тестов, инфраструктура интеграционных тестов).
- Навык `kompas-3d` — методика управления КОМПАС через MCP (базовый цикл, `validate_part`, осмотр).
- Авто-память (читай по теме перед работой): `kompas-drawing-api7`, `kompas-assembly-insert-api7`,
`kompas-step-and-assembly-api`, `kompas-hole-api7`, `kompas-variables-api5`,
`kompas-parametric-sketch-findings`. Спеки фич — `docs/superpowers/specs/`.
**Ревью-гейт (важно):** спек И реализацию каждой фичи прогонять через **три ревьюера параллельно**
Codex (`codex:codex-rescue`) + pi/GLM-5.1 + pi/Kimi-K2.6 (субагент `pi-delegate`, `--model
ollama-cloud/glm-5.1` и `--model ollama-cloud/kimi-k2.6`, `--read-only --thinking high`). См. память
`review-gate-multi-reviewer`. Находки агрегировать и оценивать по существу (receiving-code-review).
**Состояние (main = origin):** 83 MCP-инструмента, 331 тест зелёный, сборка Release чистая. Три класса:
- **Деталь** — насыщена (эскизы, формообразующие, массивы/зеркало, отверстия, уклон, переменные,
STEP, прямое редактирование B-rep, структурный осмотр). Есть навык `kompas-fdm-design` (DFM под FDM-печать).
- **Сборки** — базовое готово (вставка компонента, сопряжения coincidence/distance).
- **2D-чертёж** — виды, основная надпись, размеры (инкр. 5), текстовые обозначения (инкр. 6),
ассоциативная привязка диам./радиального к окружности (инкр. 7), формат/ориентация листа (инкр. 8),
**выноска с текстом** (`drawing_add_leader`; инкр. 9).
**Фокус — веха 2D-ЧЕРТЁЖ** (самый ценный растущий класс; деталь и сборки близки к насыщению).
Осмотри `DrawingService`/`DrawingTools` и **бэклог `docs/TODO.md`** (верх раздела «2D-ЧЕРТЁЖ»), предложи
самый ценный следующий инкремент. Возможные направления (не предрешено): **базы** (`Bases`) + **допуски
формы** (`Tolerances`) — GD&T-пара; **расширить привязку** — к дуге (`Arcs`; свой спайк: дуги одного
родителя неотличимы по центру+радиусу), угловой (`BaseObject1/2`), шероховатость (`IRough.BaseObject`);
рамка/основная надпись по ГОСТ-стилю (`LayoutLibraryFileName`/`LayoutStyleNumber`), несколько листов;
спец. выноски (позиция/клеймо/маркер). Точки входа в API разведывай сам — рефлексия по
`libs/kompas-interop/*.dll` + субагент `kompas-sdk-research`.
**Метод (кратко):** при неопределённости (привязка к геометрии, координаты, единицы) — спайк до
спека; затем краткий спек → ревью Codex → TDD (валидаторы в чистый static для unit, интеграционный
тест наследует `IntegrationTestBase`) → ревью Codex реализации → документация субагентом
`docs-maintainer` → merge/push в `main` → обнови этот хэндофф и память. Число тестов задавай по
раннеру `dotnet test`, не по числу `[Fact]`/`[Theory]`.
**Не повторять:** параметрические эскизы (переменная двигает геометрию) недостижимы через COM-API —
см. память `kompas-parametric-sketch-findings` и спек `2026-05-27-parametric-sketch-findings.md`.
Начни с обзора текущих возможностей 2D-чертежа и пробелов, предложи следующий инкремент, согласуй
направление — и приступай.
-170
View File
@@ -1,170 +0,0 @@
# Shell (операция «Оболочка») Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans. Steps use checkbox (`- [ ]`).
**Goal:** MCP-инструмент `shell` — превратить тело в оболочку заданной толщины, удалив выбранные грани.
**Architecture:** `ShellAsync` в `PartModeler.Features.cs` (паттерн `NewEntity(o3d_shellOperation)→GetDefinition→FaceArray.Add→thickness/thinType→Create`), helper `SelectFaceByIndex` в ядре, инструмент `shell` в `FeatureTools.cs`. Грани — по индексам из `list_faces`.
**Tech Stack:** .NET 8 x64, C#, COM API5 (`Kompas6API5.ksShellDefinition`, `Kompas6Constants3D.Obj3dType.o3d_shellOperation`=43).
---
## Task 1: shell end-to-end
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.cs` (helper)
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs` (метод)
- Modify: `src/Kompas.Mcp.Host/Tools/FeatureTools.cs` (инструмент)
- Test: `tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs`
- [ ] **Step 1: Падающий интеграционный тест** `FeatureOpsTests.Shell_box_hollows_with_open_top`
```csharp
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: формообразующие операции пакета B.</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class FeatureOpsTests
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly QueryService _query;
public FeatureOpsTests(KompasFixture fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
_query = new QueryService(fx.Session, fx.Dispatcher);
}
[Fact]
public async Task Shell_box_hollows_with_open_top()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Коробка 40×30×20.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddRectangleAsync(s, 0, 0, 40, 30);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 20);
var before = (await _query.GetPartInfoAsync()).Volume; // 24000
// Удаляем одну из двух плоских граней-торцов (площадь 40×30=1200) → открытая оболочка.
var faces = await _query.ListFacesAsync();
var cap = faces.First(f => f.Type == "plane" && Math.Abs(f.Area - 1200) < 1);
var id = await _modeler.ShellAsync(new[] { cap.Index }, thickness: 2, outward: false);
Assert.True(id > 0);
await _modeler.RebuildAsync();
// Оболочка 2 мм с открытым торцом: 24000 36×26×18 = 7152 мм³.
var after = (await _query.GetPartInfoAsync()).Volume;
Assert.True(after < before);
Assert.InRange(after, 7152 * 0.95, 7152 * 1.05);
}
finally { await _docs.CloseAsync(save: false); }
}
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test -c Release --filter "FullyQualifiedName~FeatureOpsTests.Shell_box_hollows_with_open_top"`
Expected: FAIL (метод `ShellAsync` не существует).
- [ ] **Step 3: Helper `SelectFaceByIndex`** в `PartModeler.cs` (рядом с `SelectEdgeByIndex`)
```csharp
/// <summary>Выбрать грань детали по индексу из list_faces (с проверкой диапазона).</summary>
private static ksEntity SelectFaceByIndex(ksPart part, int faceIndex)
{
var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection
?? throw new InvalidOperationException("Не удалось получить коллекцию граней.");
if (faceIndex < 0 || faceIndex >= faces.GetCount())
throw new ArgumentOutOfRangeException(nameof(faceIndex),
$"Индекс грани вне диапазона [0; {faces.GetCount() - 1}]. Сверьтесь с list_faces.");
return faces.GetByIndex(faceIndex) as ksEntity
?? throw new InvalidOperationException("Грань по индексу не приводится к ksEntity.");
}
```
- [ ] **Step 4: Метод `ShellAsync`** в `PartModeler.Features.cs`
```csharp
/// <summary>Превратить тело в оболочку: удалить (открыть) грани с индексами faceIndices,
/// задать толщину стенки thickness (мм). outward=false — толщина внутрь (габарит сохраняется),
/// true — наружу. Возвращает id операции.</summary>
public Task<int> ShellAsync(IReadOnlyList<int> faceIndices, double thickness, bool outward = false, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
if (thickness <= 0) throw new ArgumentOutOfRangeException(nameof(thickness), "Толщина должна быть > 0.");
ArgumentNullException.ThrowIfNull(faceIndices);
var indices = faceIndices.Distinct().ToList();
if (indices.Count == 0) throw new ArgumentException("Нужна хотя бы одна удаляемая грань.", nameof(faceIndices));
var part = GetTopPart();
// Проверяем диапазон ВСЕХ индексов до мутации модели.
var faces = indices.Select(i => SelectFaceByIndex(part, i)).ToList();
var entity = part.NewEntity((short)Obj3dType.o3d_shellOperation) as ksEntity
?? throw new InvalidOperationException("NewEntity(o3d_shellOperation) вернул null.");
var def = entity.GetDefinition() as ksShellDefinition
?? throw new InvalidOperationException("GetDefinition() оболочки вернул не ksShellDefinition.");
var arr = def.FaceArray() as ksEntityCollection
?? throw new InvalidOperationException("FaceArray() оболочки вернул не ksEntityCollection.");
foreach (var f in faces) arr.Add(f);
def.thickness = thickness;
def.thinType = !outward; // true=внутрь, false=наружу
if (!entity.Create())
throw new InvalidOperationException(
"Create() оболочки вернул FALSE (толщина больше локального радиуса/стенки или несовместимая топология?).");
var id = _nextId++;
_features[id] = entity;
return id;
}, ct);
```
- [ ] **Step 5: Инструмент `shell`** в `FeatureTools.cs` (перед `Rebuild`)
```csharp
[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);
return $"Оболочка толщиной {thickness} мм создана (удалено граней: {faceIndices.Length}), id={id}.";
}
```
- [ ] **Step 6: Запустить тест**
Run: `dotnet test -c Release --filter "FullyQualifiedName~FeatureOpsTests.Shell_box_hollows_with_open_top"`
Expected: PASS (если фактический объём иной — скорректировать ожидание по замеру, сохранив узкий допуск).
- [ ] **Step 7: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.cs src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs src/Kompas.Mcp.Host/Tools/FeatureTools.cs tests/Kompas.Mcp.Tests/Integration/FeatureOpsTests.cs
git commit -m "feat(feature): операция Оболочка (shell)"
```
---
## Task 2: документация и завершение
- [ ] Полный прогон: `dotnet test -c Release --filter "Category=Unit"` и `--filter "Category=Integration"` — всё зелёное.
- [ ] Codex-ревью реализации; внести обоснованные правки.
- [ ] `docs-delegate`: +1 инструмент (→51), +1 тест; `shell` в описание формообразующих; начат пакет B.
- [ ] Merge в `main`.
@@ -1,940 +0,0 @@
# Sketch Primitives (пакет A) — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Добавить 7 MCP-инструментов эскиза (дуга ×2, эллипс, ломаная, правильный многоугольник, сплайн, точка) поверх существующего цикла «эскиз → операция».
**Architecture:** Новые методы — тонкие обёртки над `ksDocument2D` (API5) в открытом эскизе, по образцу существующих `AddLineAsync`/`AddCircleAsync`. `ellipse`/`polygon`/`spline` используют параметрические структуры, получаемые через `KompasObject.GetParamStruct(...)` и освобождаемые в `finally`. `PartModeler` разбивается на partial-файлы (ядро/эскизы/операции). Чистые маппинги и валидация вынесены в `SketchGeometry` для unit-тестов; геометрия проверяется интеграционно (профиль → extrude → объём).
**Tech Stack:** .NET 8 (net8.0-windows, x64), C#, COM API5 (`Kompas6API5`, `Kompas6Constants`), MCP SDK `ModelContextProtocol`, xUnit.
**Подтверждено рефлексией по `libs/kompas-interop/`** (сигнатуры — дословно):
```
Int32 ksArcBy3Points(Double x1, y1, x2, y2, x3, y3, Int32 style)
Int32 ksArcByAngle(Double xc, yc, rad, f1, f2, Int16 direction, Int32 style) // углы в градусах
Int32 ksEllipse(Object par) // par = ksEllipseParam { xc, yc, A, B, angle(град), style }
Int32 ksRegularPolygon(Object par, Int16 centre) // par = ksRegularPolygonParam { count, xc, yc, ang(град), radius, describe, style }
Int32 ksNurbs(Int16 degree, Boolean close, Int32 style)
Int32 ksNurbsPoint(Object par) // par = ksNurbsPointParam { x, y, weight }
Int32 ksEndObj()
Int32 ksPoint(Double x, y, Int32 style)
```
Коды структур (`Kompas6Constants.StructType2DEnum`): `ko_EllipseParam=22`, `ko_RegularPolygonParam=92`, `ko_NurbsPointParam=18`. Стиль линии «основная» = `1`; стиль точки = `0`.
**Источники истины (во избежание расхождений):** Automation-семантику методов берём из `docs/Kompas3D_SDK/`; точные C#-имена свойств и типы — из рефлексии по `libs/kompas-interop/` (interop генерирует, например, `ksEllipseParam.A`/`.B` с заглавной — это канон для компиляции, регистр в C# важен). При конфликте имени побеждает interop.
**NURBS — порядок, не степень:** параметр `degree` метода `ksNurbs` — это *порядок* кривой (степень полинома + 1, диапазон 3..10). Кубический сплайн = порядок **4**. В плане используем константу `SplineOrder = 4`.
**Регистрация:** доп. DI не нужна — `PartModeler` уже синглтон в [Program.cs](../../../src/Kompas.Mcp.Host/Program.cs:24), инструменты подхватываются `WithToolsFromAssembly()`.
**Команды:**
- Сборка: `dotnet build -c Release`
- Unit: `dotnet test --filter "Category=Unit"`
- Integration (нужен запущенный КОМПАС): `dotnet test --filter "Category=Integration"`
---
## Task 1: Рефакторинг PartModeler в partial class
Чистое перемещение существующих методов по трём файлам — поведение не меняется. Регрессия = существующие тесты зелёные.
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.cs`
- Create: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Create: `src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs`
- [ ] **Step 1: Создать `PartModeler.Sketch.cs`** с этим заголовком и перенести в него (вырезать из `PartModeler.cs`) методы: `OpenSketchAsync`, `OpenSketchOnFaceAsync`, `OpenSketchOnFaceIndexAsync`, `CreateSketchOn`, `AddLineAsync`, `AddCircleAsync`, `AddRectangleAsync`, `AddAxisAsync`, `CloseSketchAsync` (тела без изменений).
```csharp
using System.Runtime.Versioning;
using Kompas.Mcp.Core.Interop;
using Kompas6API5;
using Kompas6Constants3D;
namespace Kompas.Mcp.Core.Modeling;
/// <summary>PartModeler: создание эскизов и добавление 2D-примитивов.</summary>
[SupportedOSPlatform("windows")]
public sealed partial class PartModeler
{
// (перенесённые методы эскизов)
}
```
- [ ] **Step 2: Создать `PartModeler.Features.cs`** с этим заголовком и перенести в него (вырезать из `PartModeler.cs`) методы: `ExtrudeAsync`, `RevolveAsync`, `FilletEdgeAsync`, `FilletEdgeIndexAsync`, `CreateFillet`, `ChamferEdgeAsync`, `ChamferEdgeIndexAsync`, `CreateChamfer`, `RebuildAsync` (тела без изменений).
```csharp
using System.Runtime.Versioning;
using Kompas6API5;
using Kompas6Constants3D;
namespace Kompas.Mcp.Core.Modeling;
/// <summary>PartModeler: формообразующие операции (выдавливание, вращение, скругление, фаска).</summary>
[SupportedOSPlatform("windows")]
public sealed partial class PartModeler
{
// (перенесённые методы операций)
}
```
- [ ] **Step 3: В `PartModeler.cs`** изменить объявление класса на `public sealed partial class PartModeler : IDisposable` и оставить только ядро: поля (`_session`, `_dispatcher`, `_nextId`, `_sketches`, `_features`), константы (`MainLineStyle`, `AxisLineStyle`), ctor, вложенный класс `SketchEntry`, helpers `ActiveDoc3D`, `GetTopPart`, `SelectEdgeByPoint`, `SelectEdgeByIndex`, `RequireSketch`, `RequireOpenSketch`, `CloseSketchCore`, `ResetCore`, `ReleaseCom`, `ResetAsync`, `Dispose`. Добавить общий helper для параметрических структур (используется в Task 5/7/8):
```csharp
/// <summary>Создать параметрическую 2D-структуру через KompasObject (API5) и привести к интерфейсу.
/// При неудачном приведении освобождает сырой RCW, чтобы не утекал COM-объект.</summary>
private T NewParam<T>(Kompas6Constants.StructType2DEnum kind) where T : class
{
var raw = _session.Kompas.GetParamStruct((short)kind);
if (raw is T typed) return typed;
ReleaseCom(raw);
throw new InvalidOperationException($"GetParamStruct({kind}) вернул не {typeof(T).Name}.");
}
```
Добавить в начало `PartModeler.cs` директиву `using Kompas6Constants;` (рядом с существующими `using`).
- [ ] **Step 4: Собрать**
Run: `dotnet build -c Release`
Expected: Build succeeded, 0 ошибок.
- [ ] **Step 5: Регрессия — существующие интеграционные тесты модели**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~ModelingTests"`
Expected: PASS (все тесты `ModelingTests` зелёные — рефакторинг ничего не сломал).
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.cs src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Core/Modeling/PartModeler.Features.cs
git commit -m "refactor(core): PartModeler в partial class (ядро/эскизы/операции)"
```
---
## Task 2: SketchGeometry (чистые маппинги/валидация) + SketchPoint
**Files:**
- Create: `src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs`
- Create: `src/Kompas.Mcp.Host/Tools/SketchPoint.cs`
- Test: `tests/Kompas.Mcp.Tests/SketchGeometryTests.cs`
- [ ] **Step 1: Написать падающий unit-тест**
```csharp
using Kompas.Mcp.Core.Modeling;
namespace Kompas.Mcp.Tests;
[Trait("Category", "Unit")]
public sealed class SketchGeometryTests
{
[Theory]
[InlineData(true, 1)]
[InlineData(false, -1)]
public void ArcDirection_maps_orientation(bool ccw, int expected)
=> Assert.Equal(expected, (int)SketchGeometry.ArcDirection(ccw));
[Theory]
[InlineData(true, false)] // вписанный → describe=false
[InlineData(false, true)] // описанный → describe=true
public void PolygonDescribe_inverts_inscribed(bool inscribed, bool expected)
=> Assert.Equal(expected, SketchGeometry.PolygonDescribe(inscribed));
[Fact]
public void RequirePositive_throws_on_zero_or_negative()
{
Assert.Throws<ArgumentOutOfRangeException>(() => SketchGeometry.RequirePositive(0, "radius"));
Assert.Throws<ArgumentOutOfRangeException>(() => SketchGeometry.RequirePositive(-1, "radius"));
Assert.Null(Record.Exception(() => SketchGeometry.RequirePositive(0.1, "radius")));
}
[Fact]
public void RequireVertexCount_throws_below_three()
{
Assert.Throws<ArgumentOutOfRangeException>(() => SketchGeometry.RequireVertexCount(2));
Assert.Null(Record.Exception(() => SketchGeometry.RequireVertexCount(3)));
}
[Fact]
public void RequirePoints_throws_on_null()
=> Assert.Throws<ArgumentNullException>(
() => SketchGeometry.RequirePoints(null!, 2, "points"));
[Fact]
public void RequirePoints_throws_when_too_few()
=> Assert.Throws<ArgumentException>(
() => SketchGeometry.RequirePoints(new[] { (0.0, 0.0) }, 2, "points"));
[Fact]
public void RequirePoints_throws_on_non_finite()
=> Assert.Throws<ArgumentException>(
() => SketchGeometry.RequirePoints(new[] { (0.0, 0.0), (double.NaN, 1.0) }, 2, "points"));
[Fact]
public void RequirePoints_passes_on_valid()
=> Assert.Null(Record.Exception(
() => SketchGeometry.RequirePoints(new[] { (0.0, 0.0), (1.0, 1.0) }, 2, "points")));
}
```
- [ ] **Step 2: Запустить — убедиться, что не компилируется/падает**
Run: `dotnet test --filter "FullyQualifiedName~SketchGeometryTests"`
Expected: FAIL (тип `SketchGeometry` не существует).
- [ ] **Step 3: Реализовать `SketchGeometry.cs`**
```csharp
namespace Kompas.Mcp.Core.Modeling;
/// <summary>Чистые помощники геометрии эскиза: маппинги контракта в параметры COM и валидация.</summary>
public static class SketchGeometry
{
/// <summary>Направление дуги для ksArcByAngle: против часовой → 1, по часовой → -1.</summary>
public static short ArcDirection(bool counterClockwise) => (short)(counterClockwise ? 1 : -1);
/// <summary>describe для ksRegularPolygon: вписанный (вершины на окружности) → false; описанный → true.</summary>
public static bool PolygonDescribe(bool inscribed) => !inscribed;
/// <summary>Требовать строго положительное значение (радиус, полуось).</summary>
public static void RequirePositive(double value, string paramName)
{
if (!(value > 0))
throw new ArgumentOutOfRangeException(paramName, value, "Значение должно быть > 0.");
}
/// <summary>Требовать >= 3 вершин для правильного многоугольника.</summary>
public static void RequireVertexCount(int count)
{
if (count < 3)
throw new ArgumentOutOfRangeException(nameof(count), count, "Число вершин должно быть >= 3.");
}
/// <summary>Валидировать список точек: не null, минимум <paramref name="min"/>, все координаты конечны.</summary>
public static void RequirePoints(IReadOnlyList<(double x, double y)> points, int min, string paramName)
{
ArgumentNullException.ThrowIfNull(points, paramName);
if (points.Count < min)
throw new ArgumentException($"Нужно минимум {min} точек, передано {points.Count}.", paramName);
for (int i = 0; i < points.Count; i++)
if (!double.IsFinite(points[i].x) || !double.IsFinite(points[i].y))
throw new ArgumentException($"Точка [{i}] имеет неконечную координату.", paramName);
}
}
```
- [ ] **Step 4: Реализовать `SketchPoint.cs`** (тип параметра MCP для polyline/spline)
```csharp
using System.ComponentModel;
using System.Text.Json.Serialization;
namespace Kompas.Mcp.Host.Tools;
/// <summary>Точка эскиза в его плоскости (мм). Элемент списка для ломаной/сплайна.
/// JSON-имена зафиксированы lowercase (x/y), чтобы схема инструмента совпадала с контрактом.</summary>
public sealed record SketchPoint(
[property: JsonPropertyName("x")][property: Description("Координата X в плоскости эскиза, мм")] double X,
[property: JsonPropertyName("y")][property: Description("Координата Y в плоскости эскиза, мм")] double Y);
```
- [ ] **Step 5: Запустить unit-тесты**
Run: `dotnet test --filter "FullyQualifiedName~SketchGeometryTests"`
Expected: PASS (все тесты `SketchGeometryTests`).
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs src/Kompas.Mcp.Host/Tools/SketchPoint.cs tests/Kompas.Mcp.Tests/SketchGeometryTests.cs
git commit -m "feat(core): SketchGeometry (маппинги/валидация) + SketchPoint"
```
---
## Task 3: Дуга по трём точкам
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Создать файл интеграционных тестов с падающим тестом**
```csharp
using Kompas.Mcp.Core.Documents;
using Kompas.Mcp.Core.Modeling;
using Kompas.Mcp.Core.Query;
namespace Kompas.Mcp.Tests.Integration;
/// <summary>Интеграция: новые примитивы эскиза (профиль → выдавливание → объём).</summary>
[Trait("Category", "Integration")]
[Collection(KompasCollection.Name)]
public sealed class SketchPrimitivesTests
{
private readonly DocumentService _docs;
private readonly PartModeler _modeler;
private readonly QueryService _query;
public SketchPrimitivesTests(KompasFixture fx)
{
_docs = new DocumentService(fx.Session, fx.Dispatcher);
_modeler = new PartModeler(fx.Session, fx.Dispatcher);
_query = new QueryService(fx.Session, fx.Dispatcher);
}
[Fact]
public async Task Arc3Points_semicircle_extrudes()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Верхний полукруг R10: дуга (10,0)→(0,10)→(-10,0) + хорда обратно.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddArc3PointsAsync(s, 10, 0, 0, 10, -10, 0);
await _modeler.AddLineAsync(s, -10, 0, 10, 0);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await _modeler.RebuildAsync();
var v = (await _query.GetPartInfoAsync()).Volume; // π·10²/2·5 ≈ 785.4
var expected = Math.PI * 100 / 2 * 5;
Assert.InRange(v, expected * 0.95, expected * 1.05);
}
finally { await _docs.CloseAsync(save: false); }
}
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Arc3Points_semicircle_extrudes"`
Expected: FAIL (метод `AddArc3PointsAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`)
```csharp
/// <summary>Добавить дугу по трём точкам (начало, точка на дуге, конец) в открытый эскиз.</summary>
public Task AddArc3PointsAsync(int sketchId, double x1, double y1, double x2, double y2, double x3, double y3, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
var editor = RequireOpenSketch(sketchId).Editor!;
if (editor.ksArcBy3Points(x1, y1, x2, y2, x3, y3, MainLineStyle) == 0)
throw new InvalidOperationException("ksArcBy3Points вернул 0 (дуга не создана — три точки коллинеарны?).");
}, ct);
```
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`, перед `Close`)
```csharp
[McpServerTool(Name = "sketch_add_arc_3points")]
[Description("Добавить дугу по трём точкам (начало, промежуточная точка на дуге, конец) в открытый эскиз. Координаты — в плоскости эскиза, мм.")]
public async Task<string> AddArc3Points(int sketchId, double x1, double y1, double x2, double y2, double x3, double y3)
{
await session.ConnectAsync();
await modeler.AddArc3PointsAsync(sketchId, x1, y1, x2, y2, x3, y3);
return "Дуга по 3 точкам добавлена.";
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Arc3Points_semicircle_extrudes"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): дуга по 3 точкам (sketch_add_arc_3points)"
```
---
## Task 4: Дуга по центру и углам
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Добавить падающий тест**`SketchPrimitivesTests`)
```csharp
[Fact]
public async Task ArcByAngle_quarter_sector_extrudes()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Сектор 90° R10 в первом квадранте: дуга (10,0)→(0,10) + два радиуса к центру.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddArcByAngleAsync(s, 0, 0, 10, 0, 90, counterClockwise: true);
await _modeler.AddLineAsync(s, 0, 10, 0, 0);
await _modeler.AddLineAsync(s, 0, 0, 10, 0);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await _modeler.RebuildAsync();
var v = (await _query.GetPartInfoAsync()).Volume; // π·10²/4·5 ≈ 392.7
var expected = Math.PI * 100 / 4 * 5;
Assert.InRange(v, expected * 0.95, expected * 1.05);
}
finally { await _docs.CloseAsync(save: false); }
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.ArcByAngle_quarter_sector_extrudes"`
Expected: FAIL (метод `AddArcByAngleAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`)
```csharp
/// <summary>Добавить дугу по центру, радиусу и углам (градусы) в открытый эскиз.</summary>
public Task AddArcByAngleAsync(int sketchId, double centerX, double centerY, double radius, double startAngle, double endAngle, bool counterClockwise = true, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
SketchGeometry.RequirePositive(radius, nameof(radius));
var editor = RequireOpenSketch(sketchId).Editor!;
var dir = SketchGeometry.ArcDirection(counterClockwise);
if (editor.ksArcByAngle(centerX, centerY, radius, startAngle, endAngle, dir, MainLineStyle) == 0)
throw new InvalidOperationException("ksArcByAngle вернул 0 (дуга не создана).");
}, ct);
```
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`)
```csharp
[McpServerTool(Name = "sketch_add_arc")]
[Description("Добавить дугу по центру, радиусу и углам (в градусах, от оси X) в открытый эскиз. counterClockwise=true — против часовой стрелки, false — по часовой. Координаты центра — в плоскости эскиза, мм.")]
public async Task<string> AddArc(int sketchId, double centerX, double centerY, double radius, double startAngle, double endAngle, bool counterClockwise = true)
{
await session.ConnectAsync();
await modeler.AddArcByAngleAsync(sketchId, centerX, centerY, radius, startAngle, endAngle, counterClockwise);
return "Дуга добавлена.";
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.ArcByAngle_quarter_sector_extrudes"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): дуга по центру и углам (sketch_add_arc)"
```
---
## Task 5: Эллипс
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Добавить падающий тест**
```csharp
[Fact]
public async Task Ellipse_extrudes_to_expected_volume()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddEllipseAsync(s, 0, 0, semiMajor: 10, semiMinor: 5);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 4);
await _modeler.RebuildAsync();
var v = (await _query.GetPartInfoAsync()).Volume; // π·10·5·4 ≈ 628.3
var expected = Math.PI * 10 * 5 * 4;
Assert.InRange(v, expected * 0.97, expected * 1.03);
}
finally { await _docs.CloseAsync(save: false); }
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Ellipse_extrudes_to_expected_volume"`
Expected: FAIL (метод `AddEllipseAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`). Использует `NewParam<T>` из Task 1; требует `using Kompas6API5;` (уже есть в файле).
```csharp
/// <summary>Добавить эллипс: центр, полуоси A/B (мм), угол наклона большой оси к X (градусы).</summary>
public Task AddEllipseAsync(int sketchId, double centerX, double centerY, double semiMajor, double semiMinor, double angle = 0, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
SketchGeometry.RequirePositive(semiMajor, nameof(semiMajor));
SketchGeometry.RequirePositive(semiMinor, nameof(semiMinor));
var editor = RequireOpenSketch(sketchId).Editor!;
ksEllipseParam? p = null;
try
{
p = NewParam<ksEllipseParam>(Kompas6Constants.StructType2DEnum.ko_EllipseParam);
// ВНИМАНИЕ: interop генерирует свойства A/B с ЗАГЛАВНОЙ (не a/b как в SDK-доках). Регистр в C# важен.
p.xc = centerX; p.yc = centerY;
p.A = semiMajor; p.B = semiMinor;
p.angle = angle; p.style = MainLineStyle;
if (editor.ksEllipse(p) == 0)
throw new InvalidOperationException("ksEllipse вернул 0 (эллипс не создан).");
}
finally { ReleaseCom(p); }
}, ct);
```
Добавить в начало `PartModeler.Sketch.cs` директиву `using Kompas6Constants;`.
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`)
```csharp
[McpServerTool(Name = "sketch_add_ellipse")]
[Description("Добавить эллипс в открытый эскиз: центр, большая полуось semiMajor, малая полуось semiMinor (мм), угол наклона большой оси к X (градусы, по умолчанию 0).")]
public async Task<string> AddEllipse(int sketchId, double centerX, double centerY, double semiMajor, double semiMinor, double angle = 0)
{
await session.ConnectAsync();
await modeler.AddEllipseAsync(sketchId, centerX, centerY, semiMajor, semiMinor, angle);
return "Эллипс добавлен.";
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Ellipse_extrudes_to_expected_volume"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): эллипс (sketch_add_ellipse)"
```
---
## Task 6: Ломаная (polyline)
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Добавить падающий тест**
```csharp
[Fact]
public async Task Polyline_closed_triangle_extrudes()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Прямоугольный треугольник (0,0)-(20,0)-(0,15), площадь 150.
var pts = new (double x, double y)[] { (0, 0), (20, 0), (0, 15) };
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddPolylineAsync(s, pts, closed: true);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await _modeler.RebuildAsync();
var v = (await _query.GetPartInfoAsync()).Volume; // 150·5 = 750
Assert.InRange(v, 750 * 0.97, 750 * 1.03);
}
finally { await _docs.CloseAsync(save: false); }
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Polyline_closed_triangle_extrudes"`
Expected: FAIL (метод `AddPolylineAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`)
```csharp
/// <summary>Добавить ломаную (цепочку отрезков) по списку точек. closed замыкает последнюю с первой.</summary>
public Task AddPolylineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
// closed-ломаная из 2 точек не образует площадь — требуем минимум 3.
SketchGeometry.RequirePoints(points, closed ? 3 : 2, nameof(points));
var editor = RequireOpenSketch(sketchId).Editor!;
for (int i = 0; i + 1 < points.Count; i++)
Seg(editor, points[i], points[i + 1]);
if (closed)
Seg(editor, points[^1], points[0]);
static void Seg(ksDocument2D ed, (double x, double y) a, (double x, double y) b)
{
if (ed.ksLineSeg(a.x, a.y, b.x, b.y, MainLineStyle) == 0)
throw new InvalidOperationException("ksLineSeg вернул 0 (сегмент ломаной не создан).");
}
}, ct);
```
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`). Добавить также приватный helper `Map` (используется и для сплайна в Task 8).
```csharp
[McpServerTool(Name = "sketch_add_polyline")]
[Description("Добавить ломаную — цепочку прямых отрезков по списку точек — в открытый эскиз. closed=true замыкает последнюю точку с первой (тогда нужно минимум 3 точки). Координаты — в плоскости эскиза, мм.")]
public async Task<string> AddPolyline(int sketchId, SketchPoint[] points, bool closed = false)
{
await session.ConnectAsync();
await modeler.AddPolylineAsync(sketchId, Map(points), closed);
return $"Ломаная из {points.Length} точек добавлена{(closed ? " (замкнута)" : "")}.";
}
private static IReadOnlyList<(double x, double y)> Map(SketchPoint[] points)
{
ArgumentNullException.ThrowIfNull(points);
return points.Select(p => (p.X, p.Y)).ToList();
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Polyline_closed_triangle_extrudes"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): ломаная по списку точек (sketch_add_polyline)"
```
---
## Task 7: Правильный многоугольник
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Добавить падающий тест**
```csharp
[Fact]
public async Task Polygon_hexagon_extrudes_to_expected_volume()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Правильный 6-угольник, вписанный в окружность R10: площадь = 0.5·6·R²·sin(60°) ≈ 259.8.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddPolygonAsync(s, 0, 0, vertexCount: 6, radius: 10, inscribed: true);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await _modeler.RebuildAsync();
var v = (await _query.GetPartInfoAsync()).Volume;
var area = 0.5 * 6 * 100 * Math.Sin(2 * Math.PI / 6);
Assert.InRange(v, area * 5 * 0.95, area * 5 * 1.05);
}
finally { await _docs.CloseAsync(save: false); }
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Polygon_hexagon_extrudes_to_expected_volume"`
Expected: FAIL (метод `AddPolygonAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`)
```csharp
/// <summary>Добавить правильный многоугольник: центр, vertexCount вершин, radius (мм),
/// inscribed=true — вершины на окружности (вписанный), false — стороны касаются (описанный),
/// angle — поворот первой вершины (градусы).</summary>
public Task AddPolygonAsync(int sketchId, double centerX, double centerY, int vertexCount, double radius, bool inscribed = true, double angle = 0, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
SketchGeometry.RequireVertexCount(vertexCount);
SketchGeometry.RequirePositive(radius, nameof(radius));
var editor = RequireOpenSketch(sketchId).Editor!;
ksRegularPolygonParam? p = null;
try
{
p = NewParam<ksRegularPolygonParam>(Kompas6Constants.StructType2DEnum.ko_RegularPolygonParam);
p.count = vertexCount; p.xc = centerX; p.yc = centerY;
p.radius = radius; p.ang = angle;
p.describe = SketchGeometry.PolygonDescribe(inscribed);
p.style = MainLineStyle;
if (editor.ksRegularPolygon(p, 0) == 0)
throw new InvalidOperationException("ksRegularPolygon вернул 0 (многоугольник не создан).");
}
finally { ReleaseCom(p); }
}, ct);
```
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`)
```csharp
[McpServerTool(Name = "sketch_add_polygon")]
[Description("Добавить правильный многоугольник в открытый эскиз: центр, vertexCount вершин (>=3), radius (мм). inscribed=true — вершины лежат на окружности радиуса (вписанный в окружность); false — стороны касаются окружности (описанный). angle — поворот первой вершины (градусы, по умолчанию 0).")]
public async Task<string> AddPolygon(int sketchId, double centerX, double centerY, int vertexCount, double radius, bool inscribed = true, double angle = 0)
{
await session.ConnectAsync();
await modeler.AddPolygonAsync(sketchId, centerX, centerY, vertexCount, radius, inscribed, angle);
return $"Правильный многоугольник ({vertexCount} вершин) добавлен.";
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Polygon_hexagon_extrudes_to_expected_volume"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): правильный многоугольник (sketch_add_polygon)"
```
---
## Task 8: Сплайн (NURBS)
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Добавить падающий тест**
```csharp
[Fact]
public async Task Spline_closed_loop_extrudes()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Замкнутый сплайн по 4 точкам вокруг начала координат → выпуклая «капля».
var pts = new (double x, double y)[] { (10, 0), (0, 10), (-10, 0), (0, -10) };
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddSplineAsync(s, pts, closed: true);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await _modeler.RebuildAsync();
// Замкнутый кубический сплайн через 4 точки даёт гладкий вогнутый контур, вписанный
// в габаритный квадрат 20×20 (площадь профиля ≈135 мм²). Проверяем, что тело построено
// и объём положителен и не превышает габарит профиля (площадь ≤ 400 → V ≤ 2000).
var v = (await _query.GetPartInfoAsync()).Volume;
Assert.InRange(v, 50 * 5, 400 * 5);
}
finally { await _docs.CloseAsync(save: false); }
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Spline_closed_loop_extrudes"`
Expected: FAIL (метод `AddSplineAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`). Добавить рядом приватную константу степени.
```csharp
private const short SplineOrder = 4; // порядок NURBS (степень+1); 4 = кубический сплайн
/// <summary>Добавить сплайн (кубический NURBS, порядок 4) через список точек. closed замыкает кривую.</summary>
public Task AddSplineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
SketchGeometry.RequirePoints(points, 2, nameof(points));
var editor = RequireOpenSketch(sketchId).Editor!;
if (editor.ksNurbs(SplineOrder, closed, MainLineStyle) == 0)
throw new InvalidOperationException("ksNurbs вернул 0 (сплайн не открыт).");
// ksNurbs открыл составной объект — ksEndObj ОБЯЗАТЕЛЕН в любом исходе, иначе редактор «застрянет».
var completed = false;
try
{
foreach (var (x, y) in points)
{
ksNurbsPointParam? np = null;
try
{
np = NewParam<ksNurbsPointParam>(Kompas6Constants.StructType2DEnum.ko_NurbsPointParam);
np.x = x; np.y = y; np.weight = 1.0;
if (editor.ksNurbsPoint(np) == 0)
throw new InvalidOperationException("ksNurbsPoint вернул 0 (узел сплайна не добавлен).");
}
finally { ReleaseCom(np); }
}
completed = true;
}
finally
{
// Закрываем составной объект всегда; ошибку завершения сообщаем только на успешном пути,
// чтобы не подменить исходное исключение из цикла.
var end = editor.ksEndObj();
if (completed && end == 0)
throw new InvalidOperationException("ksEndObj вернул 0 (сплайн не завершён).");
}
}, ct);
```
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`)
```csharp
[McpServerTool(Name = "sketch_add_spline")]
[Description("Добавить сплайн (кубический NURBS) через список точек в открытый эскиз. closed=true замыкает кривую. Координаты — в плоскости эскиза, мм.")]
public async Task<string> AddSpline(int sketchId, SketchPoint[] points, bool closed = false)
{
await session.ConnectAsync();
await modeler.AddSplineAsync(sketchId, Map(points), closed);
return $"Сплайн по {points.Length} точкам добавлен{(closed ? " (замкнут)" : "")}.";
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Spline_closed_loop_extrudes"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): сплайн NURBS по списку точек (sketch_add_spline)"
```
---
## Task 9: Вспомогательная точка
**Files:**
- Modify: `src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs`
- Modify: `src/Kompas.Mcp.Host/Tools/SketchTools.cs`
- Test: `tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs`
- [ ] **Step 1: Добавить падающий тест** (smoke: `AddPointAsync` сам бросает при `ksPoint==0`, поэтому тест доказывает успешный вызов; плюс проверяем, что точка не ломает соседний контур)
```csharp
[Fact]
public async Task Point_does_not_break_sketch()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Окружность R10 + точка в центре; выдавливание должно дать цилиндр без искажений.
var s = await _modeler.OpenSketchAsync(BasePlane.XOY);
await _modeler.AddCircleAsync(s, 0, 0, 10);
await _modeler.AddPointAsync(s, 0, 0);
await _modeler.CloseSketchAsync(s);
await _modeler.ExtrudeAsync(s, depth: 5);
await _modeler.RebuildAsync();
var v = (await _query.GetPartInfoAsync()).Volume; // π·100·5 ≈ 1570.8
var expected = Math.PI * 100 * 5;
Assert.InRange(v, expected * 0.97, expected * 1.03);
}
finally { await _docs.CloseAsync(save: false); }
}
```
- [ ] **Step 2: Запустить — убедиться, что падает**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Point_does_not_break_sketch"`
Expected: FAIL (метод `AddPointAsync` не существует).
- [ ] **Step 3: Реализовать метод модели**`PartModeler.Sketch.cs`). Добавить рядом приватную константу стиля точки.
```csharp
private const int PointStyle = 0; // системный стиль точки (см. SDK pstyles)
/// <summary>Добавить точку в открытый эскиз (опорная точка для построений).</summary>
public Task AddPointAsync(int sketchId, double x, double y, CancellationToken ct = default)
=> _dispatcher.InvokeAsync(() =>
{
var editor = RequireOpenSketch(sketchId).Editor!;
if (editor.ksPoint(x, y, PointStyle) == 0)
throw new InvalidOperationException("ksPoint вернул 0 (точка не создана).");
}, ct);
```
- [ ] **Step 4: Реализовать инструмент**`SketchTools.cs`)
```csharp
[McpServerTool(Name = "sketch_add_point")]
[Description("Добавить точку в открытый эскиз (опорная точка для построений). Координаты — в плоскости эскиза, мм.")]
public async Task<string> AddPoint(int sketchId, double x, double y)
{
await session.ConnectAsync();
await modeler.AddPointAsync(sketchId, x, y);
return "Точка добавлена.";
}
```
- [ ] **Step 5: Запустить тест**
Run: `dotnet test --filter "Category=Integration&FullyQualifiedName~SketchPrimitivesTests.Point_does_not_break_sketch"`
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src/Kompas.Mcp.Core/Modeling/PartModeler.Sketch.cs src/Kompas.Mcp.Host/Tools/SketchTools.cs tests/Kompas.Mcp.Tests/Integration/SketchPrimitivesTests.cs
git commit -m "feat(sketch): вспомогательная точка (sketch_add_point)"
```
---
## Task 10: Полный прогон и документация
**Files:**
- Modify: `CLAUDE.md`, `README.md`, `docs/ARCHITECTURE.md` (через навык `docs-delegate`)
- [ ] **Step 1: Полный прогон unit-тестов**
Run: `dotnet test --filter "Category=Unit"`
Expected: PASS (включая 4 новых `SketchGeometryTests`).
- [ ] **Step 2: Полный прогон интеграционных тестов** (нужен запущенный КОМПАС)
Run: `dotnet test --filter "Category=Integration"`
Expected: PASS (включая 7 новых `SketchPrimitivesTests` + регрессия `ModelingTests`).
- [ ] **Step 3: Обновить документацию через навык `docs-delegate`**
Передать Sonnet задачу: увеличить счётчик инструментов на +7 и тестов на +11 (4 unit + 7 integration) в `CLAUDE.md` (раздел «Current state») — сверив текущие числа по факту; добавить 7 примитивов эскиза (дуга ×2, эллипс, ломаная, многоугольник, сплайн, точка) в описание sketch-слоя в `CLAUDE.md`, `README.md` и `docs/ARCHITECTURE.md`; отметить пакет A («богаче эскизы») выполненным. Не трогать `docs/OPEN_QUESTIONS.md` сверх упоминания, что примитивы эскиза добавлены.
- [ ] **Step 4: Commit документации**
```bash
git add CLAUDE.md README.md docs/ARCHITECTURE.md
git commit -m "docs: 7 новых примитивов эскиза (пакет A) в README/ARCHITECTURE/CLAUDE"
```
- [ ] **Step 5: Завершение ветки** — использовать навык `superpowers:finishing-a-development-branch` для выбора merge/PR.
---
## Сводка контракта (для сверки типов между задачами)
Методы `PartModeler` (все `public Task ... Async(..., CancellationToken ct = default)`):
- `AddArc3PointsAsync(int sketchId, double x1, y1, x2, y2, x3, y3)`
- `AddArcByAngleAsync(int sketchId, double centerX, centerY, radius, startAngle, endAngle, bool counterClockwise = true)`
- `AddEllipseAsync(int sketchId, double centerX, centerY, semiMajor, semiMinor, double angle = 0)`
- `AddPolylineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false)`
- `AddPolygonAsync(int sketchId, double centerX, centerY, int vertexCount, double radius, bool inscribed = true, double angle = 0)`
- `AddSplineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false)`
- `AddPointAsync(int sketchId, double x, double y)`
`SketchGeometry` (public static): `short ArcDirection(bool)`, `bool PolygonDescribe(bool)`, `void RequirePositive(double, string)`, `void RequireVertexCount(int)`, `void RequirePoints(IReadOnlyList<(double x, double y)>, int, string)`.
`SketchPoint` (public record, Host): `record SketchPoint(double X, double Y)`.
MCP-инструменты (имена): `sketch_add_arc_3points`, `sketch_add_arc`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`.
@@ -1,609 +0,0 @@
# kompas-fdm-design Skill — Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Also use `superpowers:writing-skills`** when authoring/editing SKILL.md frontmatter & body.
**Goal:** Создать отдельный навык `kompas-fdm-design` — методику проектирования деталей под FDM-печать в КОМПАС-3D через MCP (правила DFM + лёгкая самопроверка геометрии), без слайсера, с числами, масштабируемыми по `w`/`h`/`θ_max`.
**Architecture:** Навык — набор Markdown-файлов в `.claude/skills/kompas-fdm-design/`: лёгкий `SKILL.md` (диспетчер) + `references/fdm-rules.md` (полный свод DFM) + `references/geometry-audit.md` (самопроверка + границы). Progressive disclosure. **Полное финальное содержимое всех трёх файлов дано дословно в задачах ниже** (по итогам 3 ревью плана: транскрипция из spec признана риском тихой потери контента — поэтому план self-contained, исполнитель пишет готовый текст, не «переносит» из spec).
**Tech Stack:** Markdown + YAML frontmatter. Кода/сборки/тестов .NET нет — навык не трогает MCP-сервер (`src/`). Проверка — структурная (`Select-String -SimpleMatch`) + по инвариантам. Опционально: live-обкатка через MCP (требует запущенного КОМПАС).
**Source of truth (контекст, НЕ копировать вручную):** spec [`docs/superpowers/specs/2026-05-27-kompas-fdm-design-skill-design.md`](../specs/2026-05-27-kompas-fdm-design-skill-design.md) (учтены 3 ревью). Прецеденты: `.claude/skills/kompas-3d/SKILL.md`, `~/.claude/skills/orcaslicer/` (SKILL.md + references/).
**Критичные инварианты (проверяются в Task 4):**
1. `θ_max` — параметр (от вертикали; дефолт 45° PLA / 40° PETG-ABS; `≈arctan(w/2h)`); нависания/teardrop/фаски/зенковки из него.
2. Teardrop: вершина на **`r / sin θ_max`** над центром, угол **`2·θ_max`** (при 45° → `√2·r`, `0.414r` над верхом окружности).
3. 90°-полка НЕ печатается (≠ «≤6 мм»); мост ≠ полка.
4. Натяг = **отрицательный** зазор (вычесть из номинала); зазоры «на сторону», диаметральный = 2×.
5. Допуск ±0.2 мм — точность изготовления, **НЕ прибавлять** к зазору посадки.
6. Компенсация Ø вертикальных отверстий: +0.2 (<4), +0.20.3 (410), +0.10.2 (>10); к диаметру (радиус +X/2).
7. Z-прочность по материалам (PLA 4055%, PETG 3550%, ABS 2035%); Z-сжатие можно, Z-растяжение/срез — нет.
8. Бор инсёрта `Ø_bore = OD_инсёрта (0…0.1) мм` (≤ OD).
9. Гео-аудит эвристический, **НЕ доказывает печатнопригодность**; путь нагрузки — из задачи, не из габарита.
10. Экспорт — `export_step`; `export_stl` НЕ вводить.
11. **Имя продукта-слайсера** (OrcaSlicer/Cura/PrusaSlicer/Bambu/…) не упоминается. Понятия «слайсинг/слайсер/g-code» допустимы для описания границ.
---
## Task 1: Скелет навыка + `SKILL.md`
**Files:** Create `.claude/skills/kompas-fdm-design/SKILL.md`
- [ ] **Step 1: Создать каталог**
```powershell
New-Item -ItemType Directory -Force ".claude/skills/kompas-fdm-design/references" | Out-Null
```
- [ ] **Step 2: Записать `SKILL.md` дословно**
````markdown
---
name: kompas-fdm-design
description: >
Методика проектирования деталей под FDM/FFF 3D-печать в КОМПАС-3D через MCP-сервер этого
проекта: правила DFM (нависания и угол θ_max, толщины стенок n·w, отверстия и teardrop,
посадки/зазоры, ориентация под прочность, elephant foot, бобышки/инсёрты/защёлки) ПЛЮС
лёгкая самопроверка геометрии инструментами осмотра. Используй, когда задача — спроектировать
или ДОВЕСТИ деталь, чтобы она хорошо ПЕЧАТАЛАСЬ на FDM. Триггеры: «сделай деталь
печатнопригодной / под FDM», «спроектируй … под печать», «напечатается ли без поддержек?»,
«подбери зазоры для печатной посадки», «как ориентировать деталь под печать», «почему деталь
плохо печатается / где будут нависания», «доведи деталь под FDM». Строит через навык kompas-3d.
НЕ для: механики построения через MCP (это kompas-3d); слайсинга/нарезки/g-code (вне границ);
поиска по справке SDK (субагент kompas-sdk-research).
---
# kompas-fdm-design — проектирование деталей под FDM-печать
## Что это (и чем НЕ является)
Методический слой **поверх** навыка `kompas-3d`. Отвечает на вопрос **«как спроектировать, чтобы
напечаталось на FDM»**, а не «чем строить».
- **`kompas-3d`** = *чем и как строить* через MCP (эскиз→операция→осмотр→`validate_part`). Этот
навык **опирается** на него для механики.
- Этот навык = *какие правила геометрии* соблюдать, чтобы FDM-печать удалась.
- **Слайсинг — вне границ.** Навык не нарезает и не оценивает g-code.
## Когда применять / когда НЕ применять
**Применять:** «сделай печатнопригодным / под FDM», «спроектируй … под печать», «напечатается без
поддержек?», «подбери зазоры печатной посадки», «как ориентировать под печать», «почему плохо
печатается», «доведи деталь под печать».
**НЕ применять:** чистая механика построения (→ `kompas-3d`); слайсинг/нарезка/g-code (вне границ);
поиск сигнатур/констант в справке SDK (→ субагент `kompas-sdk-research`).
## Калибровка (выполни первым шагом)
Все правила масштабируются от трёх параметров (часть значений — абсолютные эмпирические мм,
помечены «калибровать тестом»):
- **`w` = ширина линии ≈ диаметр сопла.** Дефолт: сопло 0.4 → `w ≈ 0.40.45 мм`. → стенки `n·w`.
- **`h` = высота слоя.** Дефолт `h ≈ 0.5·сопло` (0.2 мм); структурная печать 0.2–0.25.
- **`θ_max` = предельный угол самонесущей поверхности ОТ ВЕРТИКАЛИ.** `θ_max ≈ arctan(w/2h)`
(≈45° при `w`=0.4, `h`=0.2). **Дефолт 45°** (PLA, хороший обдув); **40°** для PETG/ABS или
толстого слоя (`h≥0.3` → ~34°). Нависания, teardrop, фаски, зенковки берут угол из `θ_max`.
- **Материал** (PLA / PETG / ABS) — модификатор зазоров/мостов/коробления/`θ_max`. См.
`references/fdm-rules.md`.
Если сопло/слой/материал не заданы — прими дефолты (сопло 0.4, `h`=0.2, PLA) и скажи об этом.
## Два правила (соблюдай всегда)
**1. Ориентация печати — первое проектное решение.**
- **Спроси у пользователя** (если не задано): главное направление рабочей нагрузки и
косметические/критичные грани. **Путь нагрузки из геометрии не выводится** — его задаёт задача.
- Реши постановку на стол (ось Z = рост слоёв). От неё зависит: где нависания; куда смотрят
отверстия (вертикальные → компенсация Ø; горизонтальные → teardrop); путь нагрузки (**держи в
XY**; Z-сжатие можно, Z-растяжение/срез — нет); плоскости сопряжения (на XY-гранях); «лесенка» на
наклонных функциональных поверхностях; если поддержки неизбежны — чтобы опорные грани были
некритичными/скрытыми.
- Зафиксируй ориентацию и проектируй под неё.
**2. Чек-лист печатнопригодности перед выдачей** (ниже). Сначала **`validate_part`** (деталь
*валидна*), затем **FDM-чек-лист** (деталь *печатнопригодна*) — разные проверки. **Гео-аудит
эвристический и не доказывает печатнопригодность** (не ловит путь нагрузки/анизотропию).
## Рабочий цикл
1. **Калибровка**: сопло→`w`; слой→`h`→`θ_max`; материал→поправки.
2. **Ориентация** (правило 1): опрос (нагрузка/косметика) → постановка, ось слоёв, сопряжения,
«лесенка», поддержки.
3. **Правила эскиза/операции** (строй через `kompas-3d`): стенки `n·w`; нависания → скос под
`θ_max`; горизонтальные отверстия → teardrop; вертикальные → компенсация Ø; фаска у основания;
зазоры посадок (со знаком); заходные фаски; мин. элементы/текст; бобышки/инсёрты/защёлки.
Числа — в `references/fdm-rules.md`.
4. **Гео-аудит** (`references/geometry-audit.md`) — инструментами осмотра.
5. **Предусловия экспорта**: единое тело/манифолд (`boolean_union` при необходимости) →
`validate_part` чисто.
6. **Чек-лист** → экспорт **через `export_step`**.
## Чек-лист печатнопригодности
- [ ] Направление нагрузки и косметические грани **получены от пользователя**; ориентация
зафиксирована; нагрузка в XY (или Z только на сжатие); сопряжения на XY-гранях.
- [ ] Стенки кратны `w` (≥2·w; несущие ≥3·w = N периметров); нет «не кратных `w`» (кроме
функциональных).
- [ ] Нет 90°-полок; нависания ≤`θ_max` или заменены скосами; мосты в пределах пролёта по короткой
стороне; внутренним поддержкам — доступ.
- [ ] Горизонтальные отверстия — teardrop/D (геометрия из `θ_max`); вертикальные — компенсация Ø;
глухие — дно ≥2–3 мм.
- [ ] Фаска у основания (elephant foot); внутренние углы ≥R0.5; опорная площадка есть.
- [ ] Посадки по таблице со **знаком** (натяг — вычесть); допуск ±0.2 **НЕ** прибавлен к зазору;
заходные фаски на сопряжениях.
- [ ] Мин. элементы/текст ≥ порогов; аспект тонких выступов ≤4–5×.
- [ ] Бобышки/инсёрты (бор ≤ OD, ставить с Z-грани)/резьба/защёлки (изгиб в XY) по правилам.
- [ ] Полости — дренаж/вент; критичные поверхности не под «лесенкой»/поддержкой.
- [ ] Предусловия экспорта: единое тело/манифолд; `validate_part` чисто.
## Гео-аудит (кратко)
Лёгкая самопроверка построенной модели **существующими** инструментами осмотра MCP:
`describe_model` / `list_faces` / `describe_face` (нависания по нормалям нижних граней; цилиндры с
горизонтальной осью → нужен teardrop), `get_bounding_box` (как ось слоёв соотносится с габаритом),
`measure` (номиналы/зазоры), `list_bodies` + `validate_part` (единое тело). **Границы и методика —
`references/geometry-audit.md`.** Аудит эвристический; истинная мин. толщина стенки и полный детект
криволинейных нависаний не решаются — это **не приговор и не доказательство печатнопригодности**.
## Связанное
- Полный численный свод DFM: [`references/fdm-rules.md`](references/fdm-rules.md).
- Рецепты самопроверки и границы: [`references/geometry-audit.md`](references/geometry-audit.md).
- Механика построения через MCP: навык **`kompas-3d`**.
- Поиск по справке SDK: субагент **`kompas-sdk-research`**.
- Дизайн навыка: `docs/superpowers/specs/2026-05-27-kompas-fdm-design-skill-design.md`.
````
- [ ] **Step 3: Проверить структуру, имя, ссылки, объём**
```powershell
$skill = ".claude/skills/kompas-fdm-design/SKILL.md"
Test-Path $skill
(Select-String -SimpleMatch -Path $skill -Pattern "name: kompas-fdm-design").Count # 1
(Select-String -SimpleMatch -Path $skill -Pattern "## Два правила").Count # 1
(Select-String -SimpleMatch -Path $skill -Pattern "## Чек-лист печатнопригодности").Count # 1
(Select-String -SimpleMatch -Path $skill -Pattern "references/fdm-rules.md").Count # >=1
(Select-String -SimpleMatch -Path $skill -Pattern "references/geometry-audit.md").Count # >=1
(Get-Content $skill | Measure-Object -Line).Lines # <= ~200 (диспетчер лёгкий)
```
Expected: `True`; счётчики `1,1,1,≥1,≥1`; строк ≤ ~200.
- [ ] **Step 4: Commit**
```powershell
git add .claude/skills/kompas-fdm-design/SKILL.md
git commit -m "feat(skill): kompas-fdm-design — диспетчер SKILL.md (правила DFM + гео-аудит)"
```
---
## Task 2: `references/fdm-rules.md` (полный свод DFM)
**Files:** Create `.claude/skills/kompas-fdm-design/references/fdm-rules.md`
- [ ] **Step 1: Записать файл дословно**
````markdown
# Свод правил DFM для FDM-печати
> Выверено 3 ревью (pi/glm-5.1, pi/kimi-k2.6, Codex). Числа для `w≈0.40.45`, `h≈0.2` (сопло 0.4).
> **Зазоры — на сторону (радиальные)**; диаметральный = 2×. **Угол нависания — от вертикали**
> (вертикаль=0°, горизонталь=90°); самонесущие — ≤ `θ_max`. Параметры `w`/`h`/`θ_max` — см.
> SKILL.md → «Калибровка». Ссылки «§N» ниже — на разделы этого файла.
## 1. Стенки и оболочки
- Толщина стенки = **`n · w`**. Мин. конструктивная — **2·w (~0.8 мм)**; несущая — **≥3·w**.
- **Маппинг стенка→периметры:** нужно `N` периметров ⇒ стенка **≥ `N·w`** (при `w`=0.45: 3 → 1.35,
4 → 1.8, 5 → 2.25 мм).
- **Не задавай толщину стенки, не кратную `w`** (напр. 0.6 при `w`=0.45): слайсер оставит зазор
или переэкструдирует. Прыгай на следующий кратный.
- **Caveat:** `n·w` — для конструктивных стенок; внешняя функциональная величина (флексура,
тепловой барьер, посадочный размер) важнее кратности.
- Одиночная стенка `1·w` — только декоративная. Узкий сквозной прорез — **≥2·w (~0.8 мм)**.
## 2. Нависания, полки, мосты (разделять!)
- **Самонесущие — поверхности ≤ `θ_max` от вертикали.** 45–60° (при дефолте) — печатается с
падением качества; **> `θ_max` существенно — поддержки** → избегать редизайном.
- **90°-полка (консоль, опора с одной стороны) НЕ печатается ни на какой длине** (миф «≤6 мм»
неверен — провисает с первого слоя). Любую горизонтальную полку: **скос под `θ_max`**, либо
**превратить в мост** (две опоры), либо поддержка.
- **Мост (bridge) — пролёт между двумя опорами на одной высоте.** При достаточном обдуве, `h≈0.2`,
консервативно (для ненастроенного слайсера): **PLA ~1525 мм, PETG ~1015 мм, ABS ~1218 мм**.
Длинные прямоугольные проёмы **ориентировать так, чтобы мост шёл по короткой стороне**; концы —
на сплошных опорах.
- Нижнюю функциональную поверхность моста — припуск **0.2–0.3 мм** на провис. *Граница:* величину
провиса из CAD не предсказать (обдув/скорость — настройки печати).
- **Внутренние/потолочные нависания хуже наружных** — потолок пазов аркой/шевроном, не плоским
пролётом > 2 мм.
- **Доступ к поддержкам:** окно во внутренней полости **≥8–10 мм**.
## 3. Отверстия
- **Вертикальные (ось ∥ Z)** печатаются уже номинала → **увеличить диаметр модели** (радиус на
половину): **+0.2 мм (Ø<4)**, **+0.20.3 мм (Ø 410)**, **+0.10.2 мм (Ø>10)**; калибровать,
критичные — рассверливать.
- **Горизонтальные (ось в XY)** → **teardrop** или **D-отверстие** (плоский верх). Мин. Ø **2 мм**.
Круглая часть тоже печатается уже → **+0.1–0.2 мм** к её Ø.
- **Геометрия teardrop:** боковины касательны окружности под углом `θ_max` к вертикали (с двух
сторон), сходятся в вершине на вертикальной оси. Высота вершины над центром = **`r / sin θ_max`**;
включённый угол при вершине = **`2·θ_max`**. При `θ_max`=45° → `r/sin45° = √2·r ≈ 1.414·r` над
центром (= **`0.414·r` над верхом окружности**), угол 90°. Низ — оставшаяся дуга окружности.
- **Глухое отверстие:** дно = внутренний мост → **толщина дна ≥2–3 мм** или купольное/
вентилируемое. Сквозные предпочтительнее.
- **Отступ от края** — через остаточную перемычку: стенка между отверстием и краем **≥2–3·w**
(лёгкая нагрузка) / больше под крепёж.
## 4. Посадки и зазоры (печатная деталь ↔ печатная деталь)
Зазор **на сторону** (радиальный); диаметральный = 2× значения:
| Посадка | Зазор/сторону | Примечание |
|---|---|---|
| **Натяг (press)** | **0.05…0 мм** (вычесть из номинала!) | короткий, PLA; иначе snap-fit (§14) |
| Переходная/плотная | 0.05–0.15 мм | |
| Скользящая | 0.150.20 мм | PLA↔PLA; контакт ≥20 мм → 0.20; PETG +0.05 |
| Свободная | 0.250.35 мм | >0.35/сторону — уже очень слабо |
- **Знак:** «натяг» = **отрицательный** зазор → вычесть из номинала (вал +/отверстие −).
Положительное число в строке press — ошибка прочтения.
- **ABS↔ABS:** +0.05/сторону. **PETG:** прессовые со временем «расслабляются».
- **Допуск точности (НЕ прибавлять к посадкам):** общий разброс FDM — **XY ±0.2 мм** (±0.1
калибровано), **Z хуже**. Это точность изготовления, не добавка к зазору.
## 5. Первый слой / стол
- **Elephant foot** — от притирки первого слоя (низкий Z-offset/переэкструзия; НЕ от высоты слоя).
Фаска по нижним рёбрам: **0.3 × 45° (калибровано)** / **0.5–1.0 × 45° (слабая калибровка)**.
- Внутренние углы у основания — **скругление ≥R0.5**.
- **Опорная площадка:** без «лезвийных» оснований; контакт хотя бы ~3 периметра. Высокие тонкие
детали — **интегральный фланец 1–2 мм** (предпочтительнее brim).
## 6. Ориентация и прочность
- **Z (межслойная) прочность от XY:** PLA ~4055%, PETG ~3550%, **ABS ~2035% (выброс)**. Несущую
нагрузку — в **XY (вдоль слоёв)**.
- **Z-сжатие допустимо** (слои в сжатии не расслаиваются); избегать **Z-растяжения и Z-среза**.
- Изгиб: слои в растяжении/сжатии, не на срез по линии слоя.
- Z-нагрузка неизбежна → **увеличить несущее сечение** ~×2 относительно XY-расчёта.
- Плоскости сопряжения — на **XY-гранях**, не на Z-боковинах.
## 7. Минимальные элементы и текст
- Выступ/штифт/ребро — **≥1·w (≥0.5 мм)**; паз/щель — **≥2·w (~0.8 мм)**.
- **Аспект тонких выступов:** высота ≤ ~4–5× базовой ширины; выше — конусность/раскос/редизайн.
- **Выпуклый** текст: штрих **≥0.5 мм**, высота **≥2·h (~0.4 мм)**, sans-serif bold.
- **Гравированный** текст: штрих **≥2·w (~0.80.9 мм)** (нужно ≥2 периметра; 0.6 мм не влезает),
глубина **≥2·h (~0.4 мм)**. (pt не используем — геометрия в мм.)
## 8. «Лесенка» (staircase) — критерий ориентации
- Наклонные/криволинейные поверхности дают ступени: глубина ≈ **`h / tan(α)`** (α — угол от
горизонтали). Пример: α=30°, `h`=0.2 → ~0.35 мм.
- Критичные (скользящие/уплотняющие/оптические) поверхности **ориентировать вертикально или
горизонтально**. Это вход в правило ориентации (SKILL.md, правило 1).
- Большой плоский **верх** без опоры коробит («подушка») — внутренние рёбра каждые ~15–20 мм или
достаточная толщина верха.
## 9. Бобышки, инсёрты, резьба
- **Саморез/винтонарезной** пилот (M3): ~Ø2.5 PLA / Ø2.6 PETG / Ø2.7 ABS; заход ≥3 мм.
- **Термоинсёрт латунный** (M3): бор **по даташиту** (типично ~Ø4.0, ±0.05); **`Ø_bore = OD_инсёрта
(0…0.1) мм`** (≤ OD, лёгкий натяг под расплав — НЕ больше OD); стенка бобышки **≥2 мм**; глубина
= длина инсёрта + 0.5 мм; **ставить с верхней (Z) грани** (не в боковину).
- **Бобышка под винт:** OD ≥ 2–3× Ø винта; не делать массивный сплошной объём (карман/оболочка).
- **Сквозное под металлический болт:** радиальный зазор 0.2–0.3 → **+0.4–0.6 мм к номиналу** болта.
- **Резьба:** не моделировать <M6 → инсёрты/саморезы. Если моделировать: **≥M6, ось вертикальная**,
зазор +0.1–0.2 мм, профиль крупный/трапецеидальный (не мелкий ISO — вершины-нависания).
## 10. Фаски vs скругления
- **Нижние (у стола) рёбра — фаска** (скругление = нулевой контакт + EF).
- **Верхние рёбра — скругление** (R0.5–2.0); **но** радиус **> ~½ толщины стенки** сам даёт
нависание > `θ_max` → тогда фаска/ступень.
- **Внутренние углы — всегда скругление ≥R0.5**.
## 11. Зенковки / цековки
- **Цековка (counterbore)** — большим Ø/полостью **вверх** (дно по телу, не мостом); глубина +0.3 мм.
- **Зенковка (countersink):** конус **вверх**; включённый угол **≤90°** → стенки ≤45° от вертикали
печатается; **>90°** → стенки-нависание → поддержка или замена цилиндрической цековкой.
## 12. Заходные фаски (assembly relief)
- На штифтах, отверстиях, инсёртах, защёлках, «ласточкиных хвостах» — **заходная фаска** (≈0.5–1 мм
× 45° или ≈ половина зазора) против задиров при сборке.
## 13. Разбиение детали и сборка из печатных частей
- Конфликт «прочная ориентация vs бесподдержечность», или крупная/коробящаяся деталь → **разбить**
на части с самоустанавливающимися стыками (печатные штифты/шпонки/замки), склейка; стыки на
XY-гранях. Зазор стыка — по §4.
## 14. Защёлки (snap-fit) / живой шарнир
- Консольная защёлка: толщина балки **≥2–3·w (≈1.0–2.0 мм)**, зацеп/возврат **0.3–0.8 мм**,
длина/толщина **≥5:1** (до 10:1), **скругление в основании ≥R0.5**.
- **Направление слоёв:** балка гнётся **в плоскости XY** (слои перпендикулярны изгибу), **не
поперёк Z** (расслоится с первого нажатия).
- **Живой шарнир** — только PLA/PP-подобные, перемычка **0.30.5 мм**; PETG/ABS не годятся.
## 15. Коробление (геометрия)
- Большие плоскости (>80×80, особенно ABS): скругления углов R3–5 + рёбра/решётка снизу.
- Радиус внешних углов: R2 (ABS) / R1 (PLA/PETG).
- Длинные тонкие пролёты (>60 мм, <2 мм) — рёбра/косынки каждые 30–40 мм; высота ребра ≤5× базы.
- Избегать сплошных кубов/плит → карман/оболочка + рёбра. Усадка: PLA ~0.3%, PETG ~0.5%, ABS ~0.8%.
- Симметрия геометрии уравновешивает усадку.
- *«Мышиные уши» (Ø8–10 мм по углам)* — **крайняя мера адгезии** (по сути brim-геометрия);
предпочтительно интегральный фланец/скругления углов.
- *Граница:* стол/корпус/обдув для ABS — настройки печати, вне навыка; здесь только геометрия.
## 16. Полые детали и гигиена модели
- **Полости:** дренаж Ø3–5 мм у **низшей** точки + вент у **высшей**.
- Допуски/зазоры — **в геометрию** (слайсер читает модель буквально).
- Раздельные тела — зазор ≥0.2 мм (общая CAD-гигиена; перед выдачей объединять — рабочий цикл,
шаг 5 в SKILL.md).
````
- [ ] **Step 2: Структурная проверка (16 разделов + 4 строки таблицы посадок)**
```powershell
$rules = ".claude/skills/kompas-fdm-design/references/fdm-rules.md"
(Select-String -Path $rules -Pattern "^## \d+\.").Count # 16 (разделы)
(Select-String -SimpleMatch -Path $rules -Pattern "| Натяг (press)").Count # 1 (строка таблицы)
(Select-String -SimpleMatch -Path $rules -Pattern "| Свободная").Count # 1
```
Expected: `16, 1, 1`. Если разделов ≠16 — потерян/задвоен раздел.
- [ ] **Step 3: Проверка инвариантов содержимого (по литералам)**
```powershell
$rules = ".claude/skills/kompas-fdm-design/references/fdm-rules.md"
foreach ($p in @(
"r / sin θ_max", "2·θ_max", "0.414·r над верхом", # inv2 teardrop
"90°-полка", "НЕ печатается ни на какой длине", # inv3 полка
"вычесть из номинала", "0.05…0 мм", # inv4 натяг
"НЕ прибавлять к посадкам", # inv5 допуск
"+0.2 мм (Ø<4)", "+0.20.3 мм (Ø 410)", # inv6 компенсация
"PLA ~4055%", "ABS ~2035%", # inv7 Z-прочность
"Ø_bore = OD_инсёрта (0…0.1) мм" # inv8 инсёрт
)) { "{0,-40} {1}" -f $p, ((Select-String -SimpleMatch -Path $rules -Pattern $p).Count) }
```
Expected: каждая строка оканчивается `1` (или больше). Любой `0` = потерянный инвариант, исправить.
- [ ] **Step 4: Commit**
```powershell
git add .claude/skills/kompas-fdm-design/references/fdm-rules.md
git commit -m "feat(skill): kompas-fdm-design — references/fdm-rules.md (полный свод DFM)"
```
---
## Task 3: `references/geometry-audit.md` (самопроверка + границы)
**Files:** Create `.claude/skills/kompas-fdm-design/references/geometry-audit.md`
**Source:** spec §9 (таблица + границы). «Как применять в цикле» — авторский раздел (привязка к
рабочему циклу), содержимое дано ниже дословно (НЕ плейсхолдер).
- [ ] **Step 1: Записать файл дословно**
````markdown
# Гео-аудит модели под FDM — что проверяемо инструментами осмотра
Лёгкая самопроверка построенной модели **существующими** инструментами осмотра MCP. Запускать на
шаге 4 рабочего цикла (см. SKILL.md), перед чек-листом и экспортом.
## Что проверяемо
| Проверка | Как | Статус |
|---|---|---|
| Нависания (приближённо) | `list_faces`/`describe_face`: для **нижних** граней угол поверхности от вертикали; > `θ_max` → флаг | ✅ плоские; ⚠️ криволинейные грубо |
| Ориентация (геом. прокси) | `get_bounding_box`: как ось слоёв соотносится с габаритом | ⚠️ длинная ось ≠ путь нагрузки |
| Горизонтальные круглые отверстия | `describe_face`: цилиндр с горизонтальной осью → «нужен teardrop» | ✅ |
| Номиналы / зазоры / габариты | `measure` между гранями; `get_bounding_box` | ✅ |
| Тело / манифолд перед выдачей | `list_bodies` (одно тело?), `validate_part` | ✅ |
## Граница честности
- **Угол нависания** мерить в **той же конвенции, что fdm-rules.md** (от вертикали; нижняя грань с
поверхностью > `θ_max` от вертикали = нависание) — не путать с углом нормали от горизонтали.
- **Путь нагрузки агент НЕ выводит из габарита** — берёт из задачи/опроса (правило 1). Длинная ось
≠ несущая.
- **Истинная мин. толщина стенки и полный детект криволинейных нависаний — не решаются** (нет
thickness/overhang-солвера).
- **Аудит эвристический и НЕ доказывает печатнопригодность** (не ловит анизотропию/путь нагрузки).
Вывод — список флагов для решения, не «приговор». Слайсер навык не зовёт намеренно.
## Как применять в цикле
1. После построения и `validate_part` — пройти таблицу выше сверху вниз.
2. Каждый флаг — сверить с соответствующим правилом `fdm-rules.md` и решить: исправить геометрию
или принять осознанно.
3. Путь нагрузки и косметические грани взять из ответа пользователя (правило 1), не из габарита.
4. Затем — чек-лист печатнопригодности (SKILL.md) → экспорт через `export_step`.
````
- [ ] **Step 2: Проверка (5 строк таблицы + границы)**
```powershell
$audit = ".claude/skills/kompas-fdm-design/references/geometry-audit.md"
(Select-String -SimpleMatch -Path $audit -Pattern "| Нависания (приближённо)").Count # 1
(Select-String -SimpleMatch -Path $audit -Pattern "| Ориентация (геом. прокси)").Count # 1
(Select-String -SimpleMatch -Path $audit -Pattern "| Тело / манифолд перед выдачей").Count # 1
(Select-String -SimpleMatch -Path $audit -Pattern "НЕ доказывает печатнопригодность").Count # 1 (inv9)
(Select-String -SimpleMatch -Path $audit -Pattern "берёт из задачи/опроса").Count # 1 (inv9)
```
Expected: все `1`.
- [ ] **Step 3: Commit**
```powershell
git add .claude/skills/kompas-fdm-design/references/geometry-audit.md
git commit -m "feat(skill): kompas-fdm-design — references/geometry-audit.md (самопроверка + границы)"
```
---
## Task 4: Верификация навыка (инварианты + триггеры + границы scope)
**Files:** Read `.claude/skills/kompas-fdm-design/**`.
- [ ] **Step 1: Прогон по инвариантам 1–11 (одна команда, таблица результатов)**
```powershell
$skill = ".claude/skills/kompas-fdm-design/SKILL.md"
$rules = ".claude/skills/kompas-fdm-design/references/fdm-rules.md"
$audit = ".claude/skills/kompas-fdm-design/references/geometry-audit.md"
$all = @($skill,$rules,$audit)
# Должны ПРИСУТСТВОВАТЬ (count >=1):
$present = @(
@{n="1 θ_max param"; f=$skill; p="θ_max ≈ arctan(w/2h)"},
@{n="2 teardrop"; f=$rules; p="r / sin θ_max"},
@{n="3 полка"; f=$rules; p="НЕ печатается ни на какой длине"},
@{n="4 натяг знак"; f=$rules; p="вычесть из номинала"},
@{n="5 допуск"; f=$rules; p="НЕ прибавлять к посадкам"},
@{n="6 комп. Ø"; f=$rules; p="+0.2 мм (Ø<4)"},
@{n="7 Z PLA"; f=$rules; p="PLA ~4055%"},
@{n="8 инсёрт бор"; f=$rules; p="Ø_bore = OD_инсёрта (0…0.1) мм"},
@{n="9 аудит≠доказ"; f=$audit; p="НЕ доказывает печатнопригодность"},
@{n="10 export_step"; f=$skill; p="export_step"}
)
foreach ($c in $present) { "{0,-16} {1}" -f $c.n, ((Select-String -SimpleMatch -Path $c.f -Pattern $c.p).Count) }
# Должны ОТСУТСТВОВАТЬ (count = 0):
"10 export_stl {0}" -f ((Select-String -SimpleMatch -Path $all -Pattern "export_stl").Count)
"11 slicer-name {0}" -f ((Select-String -Path $all -Pattern "OrcaSlicer|PrusaSlicer|Cura|Bambu|SuperSlicer|Simplify3D|Slic3r|KISSlicer|ideaMaker").Count)
```
Expected: блок «present» — все ≥1; `export_stl` = **0**; `slicer-name` = **0**. Любое нарушение —
вернуться в Task 1/2/3, исправить файл, переидти Step.
- [ ] **Step 2: Тест триггеров `description` (пары + критерий)**
Критерий: для каждого промпта оценить, перекрывает ли его текст `description` (по ключевым словам/
смыслу). ✅ = должен сработать `kompas-fdm-design`; ❌ = должен уйти другому навыку/субагенту.
| Промпт | Ожидание |
|---|---|
| «сделай эту деталь печатнопригодной» | ✅ kompas-fdm-design |
| «как ориентировать кронштейн под FDM» | ✅ kompas-fdm-design |
| «подбери зазор посадки для печати» | ✅ kompas-fdm-design |
| «подбери отверстие под термоинсёрт M3» | ✅ kompas-fdm-design |
| «напечатается ли без поддержек?» | ✅ kompas-fdm-design |
| «построй коробку выдавливанием 20×20×10» | ❌ → kompas-3d |
| «нарежь модель / сколько будет печататься» | ❌ → вне границ (слайсинг) |
| «какая сигнатура у IHole3D» | ❌ → kompas-sdk-research |
Pass-критерий: все 5 ✅ покрываются триггерами `description`; все 3 ❌ покрываются блоком «НЕ для».
Если граница размыта (ложное ✅/❌) — уточнить формулировки триггеров/«НЕ для» в SKILL.md и
переидти Step 1+2.
- [ ] **Step 3: Граница scope — не тронут `src/` (инвариант §2 spec: без новых MCP-инструментов)**
```powershell
git diff --name-only main...HEAD | Where-Object { $_ -like "src/*" }
```
Expected: **пусто** (навык не меняет MCP-сервер). Если есть `src/*` — ошибка scope.
- [ ] **Step 4: Commit правок (если были)**
```powershell
git add .claude/skills/kompas-fdm-design
git commit -m "fix(skill): kompas-fdm-design — уточнения по верификации"
```
(Если правок не было — шаг пропустить, пустой коммит не делать.)
---
## Task 5: Синхронизация документации проекта
**Files (правит субагент `docs-maintainer`):** `README.md`, `CLAUDE.md`; при необходимости
`docs/ARCHITECTURE.md`.
- [ ] **Step 1: Делегировать `docs-maintainer`**
Через `Agent` (subagent_type `docs-maintainer`) передать сводку: «Добавлен навык
`.claude/skills/kompas-fdm-design/` — методика проектирования под FDM (правила DFM + лёгкий
гео-аудит), отдельный от `kompas-3d`, без слайсера, экспорт через `export_step`. Упомянуть рядом с
описанием навыка `kompas-3d` в `CLAUDE.md` (раздел Principle/skills) и в `README.md` (где
перечислены навыки). Счётчики инструментов/тестов НЕ меняются. presentation.html не трогать.»
- [ ] **Step 2: Фолбэк, если субагент недоступен (ручная правка)**
Если `docs-maintainer` недоступен — в `CLAUDE.md` найти строку-якорь:
`On top of MCP — skill **`.claude/skills/kompas-3d/`** with a methodology` и добавить сразу после
предложения:
> Additionally, the **`.claude/skills/kompas-fdm-design/`** skill layers FDM design-for-printing methodology (DFM rules + light geometry audit) on top of `kompas-3d`; standalone, no slicer, exports via `export_step`.
В `README.md` — в разделе про навыки/skills добавить аналогичную строку про `kompas-fdm-design`
(если такого раздела нет — пропустить README).
- [ ] **Step 3: Проверить и закоммитить**
```powershell
(Select-String -SimpleMatch -Path "CLAUDE.md","README.md" -Pattern "kompas-fdm-design").Count # >=1
git add CLAUDE.md README.md docs/ARCHITECTURE.md
git commit -m "docs: упомянуть навык kompas-fdm-design (проектирование под FDM)"
```
Expected: счётчик ≥1.
---
## Task 6 (опционально, рекомендуемый follow-up): live-обкатка в `usecases/`
> **Рационал:** навык — дистилляция знаний, уже выверенная экспертными консультациями + 6 ревью
> (spec×3, план×3). Поэтому live-UC — **рекомендуемая валидация на живой модели, а не гейт**
> существования навыка (в отличие от новых MCP-механик, которые проект требует доказывать в
> `usecases/`). Требует запущенного КОМПАС. `usecases/` в .gitignore — коммит не нужен.
**Files:** Create `usecases/0003-fdm-bracket-no-supports/case.md` (из `usecases/_TEMPLATE/`).
- [ ] **Step 1: Завести кейс**
```powershell
Copy-Item -Recurse "usecases/_TEMPLATE" "usecases/0003-fdm-bracket-no-supports"
```
- [ ] **Step 2: Заполнить `case.md`** — Цель: спроектировать простой кронштейн под FDM **без
поддержек**, применяя навык. Проверить на живой модели: ориентацию (правило 1), нависания (скос
под `θ_max`), горизонтальное отверстие (teardrop по `r/sin θ_max`), фаску у основания, гео-аудит,
чек-лист, экспорт `export_step`. Заполнить разделы Цель / Промт / Статус (см. соседние кейсы
`usecases/0001`, `usecases/0002` как образец оформления).
- [ ] **Step 3: Прогнать сценарий через MCP** (если КОМПАС запущен): построить деталь правилами
навыка через `kompas-3d`, прогнать гео-аудит и чек-лист, сохранить артефакты в `artifacts/`,
экспортировать `export_step`. Зафиксировать находки в «Выводы». Подтверждённые числовые уточнения
— поднять в `references/fdm-rules.md` (и переидти Task 4 Step 1).
---
## Task 7: Финальное ревью навыка и завершение ветки
- [ ] **Step 1:** `superpowers:requesting-code-review` по диффу ветки (навык + доки) — ясность
формулировок, отсутствие плейсхолдеров, корректность кросс-ссылок.
- [ ] **Step 2:** `superpowers:writing-skills`-верификация: frontmatter валиден; `description`
срабатывает на целевых триггерах и не перехватывает чужие (Task 4 Step 2); progressive disclosure
соблюдён (SKILL.md лёгкий ≤~200 строк, числа в references).
- [ ] **Step 3: Финальный коммит (условно)**
```powershell
if ((git status --porcelain).Length -gt 0) {
git add -A; git commit -m "chore(skill): kompas-fdm-design — финал ревью"
} else { "Нет изменений — коммит не нужен" }
```
- [ ] **Step 4:** `superpowers:finishing-a-development-branch` — merge/PR по выбору пользователя.
---
## Self-Review (выполнено автором плана)
**1. Покрытие spec:** §1–§3 (цель/locked/параметры) → Task 1 (SKILL.md «Калибровка», описание).
§4 (структура файлов) → Tasks 1–3. §5 (два правила) + §6 (цикл) + §10 (чек-лист) → Task 1 (в
SKILL.md дословно). §7 (триггеры) → Task 1 frontmatter + Task 4 Step 2. §8.1–§8.16 → Task 2 (полный
текст). §9 → Task 3 (полный текст). §11 (обкатка) → Task 6 (с рационалом про опциональность). §12
(non-goals: без слайсера/без MCP-инструментов/без export_stl) → Task 4 Step 1 (export_stl=0,
slicer-name=0) + Step 3 (нет `src/*`). §13 OQ-1/OQ-2 → Task 2 Step 3 (литералы компенсации/EF в
fdm-rules.md); OQ-3 → инв.10 (export_step есть, export_stl нет). Пробелов нет.
**2. Плейсхолдеры:** все три файла даны дословно (включая «Как применять в цикле» в Task 3 —
авторский, с готовым текстом). Делегирование docs-maintainer снабжено ручным фолбэком (Task 5
Step 2) с точным якорем и текстом. Task 4 Step 2 имеет явный pass-критерий. «TODO/TBD» нет.
**3. Согласованность имён/формул:** имя `kompas-fdm-design`, пути файлов, параметры `w`/`h`/`θ_max`,
формула teardrop `r / sin θ_max` и угол `2·θ_max`, строка инсёрта `Ø_bore = OD_инсёрта (0…0.1) мм`,
знак натяга `0.05…0` — записаны единообразно в содержимом файлов (Tasks 1–3) и в литералах
проверок (Tasks 2–4). Проверочные `Select-String -SimpleMatch` используют те же литералы, что
записаны в файлы (нет regex-эскейпинга/кодпоинт-хрупкости). Заголовок плана: инварианты проверяются
в **Task 4** (исправлено).
@@ -1,198 +0,0 @@
# Agent CAD Tool Contract Catalog Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Создать полный агент-ориентированный каталог внешних MCP-методов КОМПАС-3D, включающий реализованные и необходимые нереализованные операции, оценку полноты сквозных сценариев и приоритизированные пробелы.
**Architecture:** Итоговый документ является нормативной картой внешнего контракта, а не описанием реализации. Текущий код Host служит источником истины для реализованных методов; желаемый контракт формируется по симметрии `create → inspect → update → delete`, сквозным CAD-сценариям и проверке технической реалистичности без публикации SDK-деталей.
**Tech Stack:** Markdown, PowerShell, ripgrep, C# attributes `[McpServerTool]`, локальная документация проекта.
## Global Constraints
- Итоговый файл: `docs/AGENT_CAD_TOOL_CATALOG.md`.
- В итоговом документе не должно быть COM-интерфейсов, API5/API7 и иных деталей SDK.
- Все существующие MCP-инструменты должны быть представлены ровно по одному разу.
- Нереализованные методы описываются как общий внешний CAD-контракт, а не как задача-специфичные workflow.
- Статусы: `✅ реализован`, `🟡 частично`, `⬜ не реализован`, `⛔ ограничен платформой`.
- Необходимость: `Core`, `Advanced`, `Optional`; приоритет пробелов: `P0`, `P1`, `P2`.
- Нельзя изменять существующие незакоммиченные пользовательские правки вне создаваемого каталога.
---
### Task 1: Инвентаризация реализованного внешнего контракта
**Files:**
- Read: `src/Kompas.Mcp.Host/Tools/*.cs`
- Read: `README.md`
- Read: `docs/ARCHITECTURE.md`
- Create: `docs/AGENT_CAD_TOOL_CATALOG.md`
**Interfaces:**
- Consumes: имена и описания методов из атрибутов `[McpServerTool(Name = "...")]` и `[Description("...")]`.
- Produces: доменные таблицы, в которых каждый реально зарегистрированный MCP-метод встречается ровно один раз.
- [ ] **Step 1: Получить машинный список имён методов**
Run:
```powershell
rg -o 'McpServerTool\(Name = "[^"]+"' src/Kompas.Mcp.Host/Tools | Sort-Object
```
Expected: список всех имён внешних MCP-методов из Host без зависимости от потенциально устаревшего счётчика README.
- [ ] **Step 2: Сверить количество и уникальность имён**
Run:
```powershell
$names = rg -o 'McpServerTool\(Name = "[^"]+"' src/Kompas.Mcp.Host/Tools | ForEach-Object { if ($_ -match 'Name = "([^"]+)"') { $Matches[1] } }
"total=$($names.Count) unique=$(@($names | Sort-Object -Unique).Count)"
```
Expected: `total` равно `unique`; текущее ожидаемое значение по README — 83, но источником истины остаётся фактический Host.
- [ ] **Step 3: Создать каркас каталога и внести реализованные методы**
Create `docs/AGENT_CAD_TOOL_CATALOG.md` with:
```markdown
# Каталог внешнего CAD-контракта для агента
## Назначение и критерий полноты
## Легенда
## Покрытие сквозных сценариев
## Методы внешнего контракта
### Сессия и документы
| Метод | Назначение | Статус | Необходимость | Приоритет | Пробел / ограничение |
|---|---|---|---|---|---|
```
Repeat the same table header for every domain from the approved design. Add every extracted method once, using its public behavior rather than implementation details.
- [ ] **Step 4: Проверить, что все реализованные методы попали в каталог**
Run:
```powershell
$source = rg -o 'McpServerTool\(Name = "[^"]+"' src/Kompas.Mcp.Host/Tools | ForEach-Object { if ($_ -match 'Name = "([^"]+)"') { $Matches[1] } } | Sort-Object -Unique
$catalog = rg -o '`[a-z][a-z0-9_]+`' docs/AGENT_CAD_TOOL_CATALOG.md | ForEach-Object { $_.Trim('`') } | Sort-Object -Unique
Compare-Object $source $catalog | Where-Object SideIndicator -eq '<='
```
Expected: no output.
### Task 2: Спроектировать недостающий агентский контракт
**Files:**
- Modify: `docs/AGENT_CAD_TOOL_CATALOG.md`
- Read: `docs/TODO.md`
- Read: `docs/OPEN_QUESTIONS.md`
- Read: `docs/Kompas3D_SDK/` only for internal feasibility checks
**Interfaces:**
- Consumes: реализованный каталог Task 1 и критерии полноты из дизайн-спецификации.
- Produces: полный целевой контракт с отсутствующими, частичными и платформенно ограниченными методами.
- [ ] **Step 1: Проверить симметрию каждого мутирующего семейства**
For every domain, explicitly check whether an agent can:
```text
list/describe → create → update/transform → delete → validate
```
Add a row for every missing externally observable capability. Use `🟡 частично` when an existing method covers only part of the target behavior; do not invent a second row if an extension of the existing contract is clearer.
- [ ] **Step 2: Проверить адресуемость и устойчивость ссылок**
Add required contract methods for objects that are created but cannot later be found, inspected, changed or deleted. Cover at least documents, sketch entities, features, bodies, vertices, components, mates, drawing views and drawing annotations.
- [ ] **Step 3: Проверить замкнутость пяти сквозных сценариев**
For each scenario from the approved design, list stages in the matrix and mark:
```text
полный | частичный | неполный
```
A scenario is `полный` only if the agent can inspect initial state, perform the mutation, verify the result, save it and recover from an incorrect mutation.
- [ ] **Step 4: Проверить реалистичность спорных методов**
Search the local SDK knowledge base only when feasibility is unclear. If reliable external behavior cannot be confirmed, mark the catalog row `⛔ ограничен платформой` or state that a technical spike is required. Do not copy SDK symbols or call chains into the catalog.
- [ ] **Step 5: Добавить сводный приоритет пробелов**
Add three ordered subsections:
```markdown
### P0 — замыкание автономного цикла
### P1 — типовые профессиональные сценарии
### P2 — расширение охвата
```
Every item in these lists must reference a method already present in a domain table.
### Task 3: Проверка качества и передача результата
**Files:**
- Verify: `docs/AGENT_CAD_TOOL_CATALOG.md`
- Verify: `src/Kompas.Mcp.Host/Tools/*.cs`
**Interfaces:**
- Consumes: completed catalog from Tasks 12.
- Produces: internally consistent Markdown document with evidence-backed coverage claims.
- [ ] **Step 1: Повторить автоматическую сверку реализованных имён**
Run the `Compare-Object` command from Task 1 Step 4.
Expected: no missing source methods.
- [ ] **Step 2: Проверить дубликаты строк методов**
Run:
```powershell
$rows = Select-String -Path docs/AGENT_CAD_TOOL_CATALOG.md -Pattern '^\| `([a-z][a-z0-9_]+)` \|' | ForEach-Object { $_.Matches[0].Groups[1].Value }
$rows | Group-Object | Where-Object Count -gt 1 | Select-Object Name, Count
```
Expected: no output, except deliberate method-family extension rows must instead be consolidated into one row.
- [ ] **Step 3: Проверить отсутствие внутренних SDK-деталей**
Run:
```powershell
rg -n 'API5|API7|COM|I[A-Z][A-Za-z0-9]+|ks[A-Z][A-Za-z0-9]+' docs/AGENT_CAD_TOOL_CATALOG.md
```
Expected: no implementation references; ordinary Russian words that accidentally match the expression are reviewed manually.
- [ ] **Step 4: Проверить Markdown и пробельные ошибки**
Run:
```powershell
git diff --check -- docs/AGENT_CAD_TOOL_CATALOG.md
```
Expected: exit code 0 and no output.
- [ ] **Step 5: Проверить итоговую полноту вручную**
Confirm all acceptance criteria from `docs/superpowers/specs/2026-07-17-agent-cad-tool-contract-catalog-design.md`, then report:
```text
implemented methods / partial methods / missing methods / platform-limited methods
P0 / P1 / P2 gaps
scenario coverage: full / partial / incomplete
```
@@ -1,130 +0,0 @@
# Codex SDK Research Agent Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Turn the project-scoped `kompas-sdk-research` definition into a Codex-native, explicitly modeled, read-only documentation research agent.
**Architecture:** Keep the existing single custom-agent TOML and its KOMPAS SDK research methodology. Add Codex session overrides for model, reasoning effort, and sandboxing, and remove copied Claude-specific model terminology without changing `.claude/agents/kompas-sdk-research.md`.
**Tech Stack:** Codex custom-agent TOML, PowerShell, Python 3 `tomllib`, ripgrep, Git.
## Global Constraints
- Use `model = "gpt-5.6-terra"`.
- Use `model_reasoning_effort = "medium"`.
- Use `sandbox_mode = "read-only"`.
- Keep `docs/Kompas3D_SDK/` as the canonical COM API5/API7 documentation source.
- Do not add web research, workspace editing, KOMPAS geometry construction, or KsAPI research to the agent.
- Leave `.claude/agents/kompas-sdk-research.md` unchanged.
- The parent task remains responsible for interop reflection checks when documentation and generated .NET names may differ.
---
### Task 1: Adapt and verify the Codex custom agent
**Files:**
- Modify: `.codex/agents/kompas-sdk-research.toml`
- Verify unchanged: `.claude/agents/kompas-sdk-research.md`
**Interfaces:**
- Consumes: Codex standalone custom-agent fields `name`, `description`, `developer_instructions`, `model`, `model_reasoning_effort`, and `sandbox_mode`.
- Produces: project agent `kompas-sdk-research` running with `gpt-5.6-terra`, medium reasoning, and a read-only sandbox.
- [ ] **Step 1: Run the pre-change configuration check and confirm it fails**
Run:
```powershell
@'
import pathlib
import tomllib
path = pathlib.Path('.codex/agents/kompas-sdk-research.toml')
data = tomllib.loads(path.read_text(encoding='utf-8'))
text = path.read_text(encoding='utf-8')
assert data['model'] == 'gpt-5.6-terra'
assert data['model_reasoning_effort'] == 'medium'
assert data['sandbox_mode'] == 'read-only'
assert not {'Haiku', 'Opus', 'Claude'} & set(text.replace('(', ' ').replace(')', ' ').split())
'@ | python -
```
Expected: FAIL with `KeyError: 'model'` because the Codex-specific model fields have not been added yet.
- [ ] **Step 2: Add Codex-native model and isolation settings**
Apply this exact header change:
```diff
name = "kompas-sdk-research"
+model = "gpt-5.6-terra"
+model_reasoning_effort = "medium"
+sandbox_mode = "read-only"
description = "..."
```
Replace the `description` value with this exact single-line TOML string:
```toml
description = "ОБЯЗАТЕЛЬНО делегируй этому read-only субагенту ЛЮБОЙ поиск по справке COM API КОМПАС в MD-базе docs/Kompas3D_SDK/ — НЕ ищи по справке в основной задаче. Срабатывает ПРОАКТИВНО, без явной просьбы пользователя: как только при планировании или реализации фичи понадобилась сигнатура метода (Automation/COM), значения перечисления (Obj3dType, ksHoleTypeEnum, ST_MIX_*, стили линий…), нужный интерфейс под задачу, единицы измерения или цепочка вызовов — СНАЧАЛА делегируй исследование этому агенту, ПОТОМ пиши код. Агент возвращает сжатую выжимку, а основная задача при необходимости перепроверяет спорные детали рефлексией по libs/kompas-interop/*.dll. Триггеры: «нужна сигнатура …», «какие значения enum …», «каким интерфейсом сделать X через API КОМПАС», «что возвращает <метод>», «как через COM API построить …», «найди в справке КОМПАС». НЕ для построения геометрии в КОМПАС и НЕ для KsAPI (Qt/C++ — в MD-базе его нет)."
```
Replace the copied provider-specific sentence in `developer_instructions`:
```diff
-Тебя вызывают, чтобы основная модель (Opus) не тратила контекст на поиск:
+Тебя вызывают, чтобы основная задача не тратила контекст на поиск:
```
Keep the remaining SDK database structure, lookup rules, response contract, and reflection warning unchanged.
- [ ] **Step 3: Parse the TOML and verify the selected Codex settings**
Run:
```powershell
@'
import pathlib
import tomllib
path = pathlib.Path('.codex/agents/kompas-sdk-research.toml')
data = tomllib.loads(path.read_text(encoding='utf-8'))
assert data['name'] == 'kompas-sdk-research'
assert data['model'] == 'gpt-5.6-terra'
assert data['model_reasoning_effort'] == 'medium'
assert data['sandbox_mode'] == 'read-only'
assert data['description']
assert data['developer_instructions']
print('Codex agent configuration: OK')
'@ | python -
```
Expected: `Codex agent configuration: OK`.
- [ ] **Step 4: Check for stale Claude terminology and unintended changes**
Run:
```powershell
$matches = rg -n "Haiku|Opus|Claude" '.codex/agents/kompas-sdk-research.toml'
if ($LASTEXITCODE -eq 0) { $matches; throw 'Stale Claude terminology remains' }
if ($LASTEXITCODE -ne 1) { throw 'ripgrep failed' }
git diff --check
git diff --exit-code -- '.claude/agents/kompas-sdk-research.md'
git diff -- '.codex/agents/kompas-sdk-research.toml'
```
Expected: no stale-term output, `git diff --check` succeeds, the Claude-file diff is empty, and the final diff contains only the planned Codex-agent changes.
- [ ] **Step 5: Commit the Codex agent and implementation plan**
Run:
```powershell
git add -- '.codex/agents/kompas-sdk-research.toml' 'docs/superpowers/plans/2026-07-17-codex-sdk-research-agent.md'
git commit -m "chore: настроить Codex-агент поиска SDK"
```
Expected: one commit containing only the Codex agent definition and this implementation plan. Existing unrelated workspace changes remain unstaged.
File diff suppressed because it is too large Load Diff
@@ -1,60 +0,0 @@
# Дизайн: смещённая плоскость + операция по сечениям (loft)
**Дата:** 2026-05-26
**Статус:** на ревью (автономный режим; ревьюер — Codex)
## Цель
Два связанных инструмента:
1. **`sketch_create_on_offset_plane`** — эскиз на плоскости, смещённой от базовой на `offset` мм
(вспомогательная геометрия; снимает ограничение «только 3 базовые плоскости»).
2. **`loft`** — операция по сечениям (несколько эскизов-сечений → сплошное тело). Сечения
строятся на параллельных смещённых плоскостях, что и разблокирует первый инструмент.
## Сигнатуры COM (рефлексия interop)
```
Obj3dType.o3d_planeOffset = 14
ksPlaneOffsetDefinition:
double offset
bool direction
bool SetPlane(object basePlane) // базовая плоскость (o3d_planeXOY/XOZ/YOZ)
Obj3dType.o3d_baseLoft = 30
ksBaseLoftDefinition:
bool SetLoftParam(bool closed, bool flipVertex, bool autoPath)
bool SetThinParam(bool thin, short thinType, double normal, double reverse)
object Sketchs() // ksEntityCollection эскизов-сечений
```
## Новые MCP-инструменты
| Инструмент | Параметры | Поведение |
|---|---|---|
| `sketch_create_on_offset_plane` | `basePlane: string (XOY/XOZ/YOZ)`, `offset: double`, `direction: bool = true` | Создать смещённую плоскость (`o3d_planeOffset`: база + offset мм + направление) и открыть на ней эскиз. Возвращает id эскиза (как `sketch_create`). |
| `loft` | `sketchIds: int[]`, `closed: bool = false` | Операция по сечениям: построить сплошное тело по ≥2 закрытым эскизам-сечениям (в порядке списка). `closed` — замкнуть в кольцо. Возвращает id операции. |
## Реализация
- **`OpenSketchOnOffsetPlaneAsync(BasePlane, double offset, bool direction, ct)`** в
`PartModeler.Sketch.cs`: `NewEntity(o3d_planeOffset)``ksPlaneOffsetDefinition`
(`SetPlane(GetDefaultEntity(базовая))`, `offset`, `direction`) → `Create()``CreateSketchOn(part, planeEntity)`.
Валидация `double.IsFinite(offset)`.
- **`LoftAsync(IReadOnlyList<int> sketchIds, bool closed, ct)`** в `PartModeler.Features.cs`:
закрыть все эскизы; `NewEntity(o3d_baseLoft)``ksBaseLoftDefinition``Sketchs()` (null-check)
`Add` каждого `sketch.Entity` в порядке списка → `SetLoftParam(closed, false, true)`
`SetThinParam(false, 0, 0, 0)``Create()`. Валидация: `sketchIds` не null, `Distinct` ≥ 2;
каждый id есть в реестре (`RequireSketch`).
- Инструменты в `SketchTools.cs` (`sketch_create_on_offset_plane`) и `FeatureTools.cs` (`loft`).
- Транзитные RCW не освобождаются — консистентно (долг v2-2).
## Тестирование
**Integration** (`FeatureOpsTests`):
1. `OffsetPlane_sketch_extrudes_at_height`: эскиз на XOY+offset 20 → окружность R5 → extrude 10 →
габарит по Z ≈ [20; 30] (`box.MinZ ≈ 20`), объём ≈ π·25·10.
2. `Loft_two_sections_makes_solid`: сечение 1 — квадрат 20×20 на XOY (offset 0, через
`sketch_create_on_offset_plane` с offset 0 или базовый `sketch_create`); сечение 2 — квадрат
20×20 на XOY+offset 40 → `loft([s1,s2])` → призма ~20×20×40, объём ≈ 16000 мм³
(`InRange ±10%`, т.к. форма сечения сохраняется). Если loft капризничает на квадратах —
проверить `volume > 0` и габарит Z ≈ 40.
@@ -1,128 +0,0 @@
# Дизайн: пакет C — массивы и зеркало (linear/circular pattern, mirror)
**Дата:** 2026-05-26
**Статус:** на ревью (автономный режим; ревьюер — Codex)
## Цель
Добавить класс операций «размножение»: **линейный массив** (по сетке вдоль координатной
оси), **круговой массив** (вокруг координатной оси) и **зеркальное отражение** (операций
относительно координатной плоскости; и тела целиком). Самый ценный недостающий класс
(NEXT-SESSION приоритет 1). Реализуется тремя инкрементами, по одной операции за раз.
Все три операции в API5 строятся одним паттерном: `NewEntity(o3d_*)``GetDefinition()`
задать **источник** (коллекция копируемых операций / тел) + **направление** (ось/плоскость) →
`Create()` → регистрация в `_features`. Источник копирования — `ksEntity` ранее созданных
операций, на которые ссылаемся по их id из реестра `_features` (те же id, что возвращают
extrude/revolve/fillet/…).
## Сигнатуры COM (рефлексия interop + справка SDK)
```
Obj3dType.o3d_meshCopy = 35 // массив по сетке (линейный)
Obj3dType.o3d_circularCopy = 36 // круговой массив
Obj3dType.o3d_mirrorOperation = 48 // зеркальная копия выбранных операций
Obj3dType.o3d_mirrorAllOperation = 49 // зеркально отразить тело(а) целиком
Obj3dType.o3d_axisOX/OY/OZ = 71/72/73 // координатные оси (GetDefaultEntity)
Obj3dType.o3d_planeXOY/XOZ/YOZ = 1/2/3 // координатные плоскости
ksMeshCopyDefinition: // линейный (по сетке)
bool SetAxis1(object axisOrEdge) // ось 1 (коорд. ось или прямое ребро)
bool SetAxis2(object axisOrEdge) // ось 2 (для 2D-сетки)
bool SetCopyParamAlongAxis(bool firstAxis, double angle, int count, double step, bool factor)
int count1/count2; double step1/step2; double angle1/angle2; bool factor1/factor2
bool geomArray; bool insideFlag
object OperationArray() // ksEntityCollection источников
ksCircularCopyDefinition: // круговой
bool SetAxis(object axis) // ось вращения
bool SetCopyParamAlongDir(int count, double step, bool factor, bool dir)
int count1/count2; double step1/step2; bool factor1/factor2; bool geomArray; bool inverce
object GetOperationArray() // ksEntityCollection источников
ksMirrorCopyDefinition: // зеркало выбранных операций
bool SetPlane(object plane) // плоскость симметрии (коорд. или плоская грань)
object GetOperationArray() // ksEntityCollection источников
ksMirrorCopyAllDefinition: // зеркало тела(а) целиком
bool SetPlane(object plane)
object ChooseBodies() // ksChooseBodies (необяз.: все тела по умолчанию)
```
**Семантика (из справки, проверить эмпирически):**
- `count` — количество **всех** экземпляров, **включая исходный** (count=3 → +2 копии).
- линейный `step` — мм между **соседними** при `factor=false`; круговой `step` — **угол в
градусах** между соседними при `factor=false`.
- `count` включает оригинал → требуем `count >= 2`.
## Новые MCP-инструменты (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `linear_pattern` | `featureIds: int[]`, `axis: "X"\|"Y"\|"Z"`, `count: int`, `step: double`, `geometric=false` | Линейный массив: размножить операции `featureIds` вдоль координатной оси `axis`, `count` экз. всего (вкл. исходную, ≥2), шаг `step` мм между соседними. `geometric` → быстрое геом. копирование. Возвращает id. |
| `circular_pattern` | `featureIds: int[]`, `axis: "X"\|"Y"\|"Z"`, `count: int`, `step=` (угол, °), `reverse=false`, `geometric=false` | Круговой массив вокруг координатной оси `axis`, `count` экз. всего (≥2), угловой шаг `step` ° между соседними (по умолчанию 360/count для равномерного — см. ниже). `reverse` — обратное направление. Возвращает id. |
| `mirror_operation` | `featureIds: int[]`, `plane: "XOY"\|"XOZ"\|"YOZ"` | Зеркальная копия операций `featureIds` относительно координатной плоскости `plane`. Возвращает id. |
| `mirror_body` | `plane: "XOY"\|"XOZ"\|"YOZ"` | Зеркально отразить всё тело относительно плоскости (получить симметричную деталь). Возвращает id. |
**Решения (YAGNI / инкрементальность):**
- **Ось/плоскость — только координатные** (X/Y/Z, XOY/XOZ/YOZ) на первом этапе: детерминированно,
легко тестируется, покрывает большинство случаев. Выбор по ребру/грани — отдельная задача позже.
- **Линейный массив — 1D** (одна ось): `SetAxis1` + `SetCopyParamAlongAxis(true,…)`, `count2=1`
(второе направление выключено). 2D-сетку добавим позже при необходимости.
- `factor=false` (шаг между соседними — интуитивнее для пользователя).
- **Круговой — задаём свойства напрямую** (`SetCopyParamAlongDir` неоднозначен: `dir`/radial vs
annular): `count1=1` (радиально — один «ряд»), `count2=count` (кольцевое — N вокруг оси),
`step2=step` (°), `factor2=false`, `inverce=reverse`. Угловой шаг задаёт агент (например 90).
- `mirror_body` использует `ksMirrorCopyAllDefinition` + `SetPlane`. В API5-interop у определения
**нет** `SaveInitialObjects` (по рефлексии — только `ChooseBodies/Get/SetPlane`); поведение
«оригинал + зеркало» проверяем эмпирически. `ChooseBodies` дорефлексировать в инкременте 3
(для односложного тела, вероятно, не требуется; если `Create` упадёт — выставить `ksAllBodies`).
- `geomArray` дефолт `false` (полное копирование операции — надёжнее для несложных тел).
### Открытые API-тонкости (проверяются интеграционными тестами)
- `count` включает оригинал — подтверждается тестом (count=3 → 3 экз.). Если нет — поправить.
- `count2=1` действительно выключает 2-е направление линейного массива.
- Круговой: `count2=count`/`count1=1` даёт чистый круговой массив (без радиального дубля).
- `mirror_body` оставляет исходное тело (а не заменяет его).
## Реализация
- **Enum** `CoordinateAxis { X, Y, Z }` + `CoordinateAxes.ToObj3dType/Parse` — новый файл
`Core/Modeling/CoordinateAxis.cs` (по аналогии с `BasePlane.cs`).
- **Методы** в `PartModeler.Features.cs`:
- `LinearPatternAsync(IReadOnlyList<int> featureIds, CoordinateAxis axis, int count, double step, bool geometric, CancellationToken)`
- `CircularPatternAsync(IReadOnlyList<int> featureIds, CoordinateAxis axis, int count, double step, bool reverse, bool geometric, CancellationToken)`
- `MirrorOperationAsync(IReadOnlyList<int> featureIds, BasePlane plane, CancellationToken)`
- `MirrorBodyAsync(BasePlane plane, CancellationToken)`
- **Хелпер** `RequireFeature(int id)` в `PartModeler.cs` (по аналогии с `RequireSketch`):
`_features.TryGetValue``ksEntity` или `KeyNotFoundException`.
- **Валидация** (чистая, `SketchGeometry`): `RequireMin(int value, int min, string)` для `count>=2`;
`RequirePositive(step, …)` (ловит NaN). Списки `featureIds`: not null, `Distinct`, не пуст,
все id существуют (проверка до мутации модели — собрать `ksEntity` заранее).
- **Инструменты** в `FeatureTools.cs`: `linear_pattern`, `circular_pattern`, `mirror_operation`,
`mirror_body`; `axis`/`plane` парсятся из строки (`CoordinateAxes.Parse`/`BasePlanes.Parse`).
- Имена методов коллекции в interop различаются: MeshCopy — `OperationArray()`; Circular и
Mirror — `GetOperationArray()` (по рефлексии). Все → `as ksEntityCollection ?? throw`.
- Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
- `Create()==FALSE``InvalidOperationException` с подсказкой (несовместимая геометрия / шаг
ведёт к самопересечению / неверная ось).
## Тестирование (Integration, `FeatureOpsTests`)
Паттерн: `CreateAsync(Part)` → построить базовую операцию (запомнить её id) → массив/зеркало →
`RebuildAsync` → проверить объём (`GetPartInfoAsync`) и габарит (`GetBoundingBoxAsync`); `finally CloseAsync`.
1. **linear_pattern**: цилиндр R5×H10 в центре (V₁≈785 мм³) → `linear_pattern([cyl], X, 3, 30)`
3 непересекающихся цилиндра → V≈3·785=2356 (`InRange ±5%`); габарит X≈70 (от −5 до 65).
2. **circular_pattern**: цилиндр R3×H10 со смещением (центр (20,0)) → `circular_pattern([cyl], Z, 3, 90)`
3 цилиндра в (20,0),(0,20),(20,0) (count=3, несимметрично → осмысленный поворот, не «полный
круг») → V≈3·283=849 (`±5%`); габарит X≈46 (от 23 до 23), SizeY≈26 (от 3 до 23).
3. **mirror_operation** (две исходные операции — проверяет заполнение `ksEntityCollection`):
цилиндр R3 центр (20,0) + цилиндр R2 центр (10,0), оба H10 → `mirror_operation([c1,c2], YOZ)`
копии в (20,0),(10,0) → V≈2·(283+125.7)=817 (`±5%`); габарит X≈46.
4. **mirror_body**: цилиндр R3×H10 центр (20,0) → `mirror_body(YOZ)` → оригинал + зеркало в
(20,0) → V≈2·283=565 (`±5%`); габарит X≈46.
Ожидаемые объёмы считаем аналитически, сверяем `InRange ±5%`. Открытые тонкости (count вкл.
оригинал; count2=1; круговой count1=1/count2=N; mirror_body оставляет оригинал) — проверяются
этими тестами; при расхождении корректируем по факту, сохранив суть проверки.
@@ -1,70 +0,0 @@
# Дизайн: пакет B (шаг 2) — операция «Ребро жёсткости» (rib)
**Дата:** 2026-05-26
**Статус:** на ревью (автономный режим; ревьюер — Codex)
## Цель
Добавить формообразующую операцию **«Ребро жёсткости»** (rib) — достроить тонкую стенку от
разомкнутого контура эскиза до тела. Второй шаг пакета B. Один MCP-инструмент `rib`.
## Сигнатуры COM (рефлексия interop + SDK-база + ksConstants3D.h)
```
Obj3dType.o3d_ribOperation = 44
ksRibDefinition:
int index // сегмент эскиза, задающий уклон (0 — первый сегмент)
double angle // угол уклона стенок, градусы (0 — вертикальные)
int side // направление достройки: ksRibSideEnum
bool SetSketch(object) / GetSketch()
bool SetThinParam(short thinType, double normalThickness, double reverseThickness)
ksRibSideEnum: ksRibSideLeft=0, ksRibSideRight=1, ksRibSideUp=2, ksRibSideDown=3
Direction_Type (thinType): dtNormal=0, dtReverse=1, dtBoth=2, dtMiddlePlane=3
```
**Паттерн:** `NewEntity(o3d_ribOperation)``GetDefinition()``SetSketch`
`index`/`angle`/`side`/`SetThinParam``Create()`. Порядок: сначала `SetSketch`, затем
параметры, затем `Create()`.
**Эскиз:** разомкнутый контур (отрезок/ломаная) на плоскости, пересекающей тело; КОМПАС
сам «дотягивает» ребро до тела.
## Новый MCP-инструмент (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `rib` | `sketchId: int`, `thickness: double`, `side: string = "up"`, `symmetric: bool = true`, `angle: double = 0` | Построить ребро жёсткости от разомкнутого контура эскиза `sketchId` до тела. `thickness` — толщина стенки (мм). `side` ∈ {left,right,up,down} — направление достройки. `symmetric=true` — толщина симметрична плоскости эскиза; `false` — в одну сторону. `angle` — уклон стенок (°). |
**Маппинги:**
- `side`: `left→0, right→1, up→2, down→3` (через `SketchGeometry.RibSide(string)`, unit-тест).
- `symmetric=true → SetThinParam(dtBoth=2, thickness/2, thickness/2)`;
`false → SetThinParam(dtNormal=0, thickness, 0)`.
- `index = 0` (фиксировано — первый сегмент контура).
## Реализация
- **Метод** `RibAsync(int sketchId, double thickness, string side, bool symmetric, double angle, CancellationToken)`
в `PartModeler.Features.cs`. Эскиз закрывается перед операцией (`CloseSketchCore`, как
`ExtrudeAsync`/`RevolveAsync`); берётся `sketch.Entity` из реестра по id. Возвращает id операции.
- **Маппинг side** — `SketchGeometry.RibSide(string)` (public static, unit-тест; бросает
`ArgumentException` на неизвестное значение).
- **Инструмент** `rib` в `FeatureTools.cs`.
- **Валидация** (inline): `thickness > 0`; `side` — известное значение (через `RibSide`).
`Create()==FALSE``InvalidOperationException` (контур не пересекает тело / некорректное
направление?).
- Транзитный `ksRibDefinition` не освобождается — консистентно с прочими операциями (долг v2-2).
## Тестирование
**Unit:** `SketchGeometry.RibSide` — маппинг строк в коды и бросок на неизвестном значении.
**Integration** (`FeatureOpsTests`): Г-образный уголок (L-профиль на XOZ, замкнутая ломаная
`(0,0)(40,0)(40,5)(5,5)(5,40)(0,40)`, extrude по Y на 30 → объём 375·30=11250) → эскиз ребра
на XOZ: разомкнутый отрезок-диагональ во внутреннем углу (например `(25,10)→(10,25)`, **висит в зазоре, не касаясь полок**) →
`RibAsync(sketchId, thickness=5, side="left", symmetric=false)` → объём увеличивается
(добавлена косынка): `after > before`, прирост правдоподобный (`after ∈ (before, before·1.3)`).
**Проверено эмпирически (важно для контракта):** контур ребра должен **висеть в зазоре**
между полками (не касаться граней тела концами — иначе `Create()==FALSE`); КОМПАС сам
дотягивает полотно до тела в направлении `side`. Для косынки во внутреннем углу сработало
`side="left"`, `symmetric=false` (толщина в прямом направлении нормали плоскости).
@@ -1,88 +0,0 @@
# Дизайн: пакет B (шаг 1) — операция «Оболочка» (shell)
**Дата:** 2026-05-26
**Статус:** на ревью (автономный режим; ревьюер — Codex)
**Контекст:** kompas3d-mcp — MCP-сервер для КОМПАС-3D через COM API (.NET 8, C#).
## Цель
Добавить формообразующую операцию **«Оболочка»** (придать стенкам толщину, удалив выбранные
грани) — первый шаг пакета B «формообразующие». Самая востребованная операция этого пакета;
опирается на уже готовый выбор грани по индексу из `list_faces`, без мульти-эскизной
оркестрации (нужной для loft/sweep). Один MCP-инструмент `shell`.
## Не входит в объём (YAGNI)
- Остальные операции пакета B (rib, draft, loft, sweep, hole) — отдельные шаги.
- Переменная толщина по граням (`ksShellDefinition` задаёт одну общую `thickness`).
- Закрытая оболочка без удаляемых граней — КОМПАС требует ≥1 удаляемую грань.
## Сигнатуры COM (подтверждены: рефлексия interop + SDK-база + пример Step3d1.cs)
```
Obj3dType.o3d_shellOperation = 43
ksShellDefinition:
double thickness // толщина стенки, мм
bool thinType // true = внутрь (габарит сохраняется), false = наружу
object FaceArray() // ksEntityCollection удаляемых (открываемых) граней; нужна >=1
```
**Паттерн** (из `Samples/CSharp.zip → Step3d1.cs`):
```
ksEntity ent = part.NewEntity(o3d_shellOperation)
ksShellDefinition def = ent.GetDefinition()
ksEntityCollection faces = def.FaceArray()
faces.Add(<грань>) // одна или несколько удаляемых граней
def.thickness = t; def.thinType = !outward
ent.Create()
```
Порядок: сначала заполнить `FaceArray`, затем `thickness`/`thinType`, затем `Create()`.
## Новый MCP-инструмент (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `shell` | `faceIndices: int[]`, `thickness: double`, `outward: bool = false` | Превратить тело в оболочку толщиной `thickness` мм, удалив (открыв) грани с указанными индексами из `list_faces`. `outward=false` — толщина внутрь (габарит сохраняется), `true` — наружу. |
**Маппинг:** `thinType = !outward`.
**Выбор граней:** по стабильному индексу из `list_faces` (как `sketch_create_on_face_index`,
`fillet_edge_index`). Индексы валидны, пока геометрия не менялась — берутся из свежего
`list_faces`. Несколько граней → несколько `FaceArray.Add`.
## Реализация
- **Метод модели** `ShellAsync(IReadOnlyList<int> faceIndices, double thickness, bool outward, CancellationToken)`
в `PartModeler.Features.cs` (формообразующая). Возвращает id операции (как extrude/revolve).
- **Helper** `SelectFaceByIndex(ksPart, int)` в ядре `PartModeler.cs` — аналог существующего
`SelectEdgeByIndex` (диапазон-проверка + `EntityCollection(o3d_face).GetByIndex`).
- **Инструмент** `shell` в `FeatureTools.cs` (как `extrude_boss`/`revolve_boss`).
- **Валидация** (inline, как `ExtrudeAsync`/`RevolveAsync`): `thickness > 0`; `faceIndices`
не null и не пуст; **каждый индекс проверяется на диапазон до мутации модели**
(`SelectFaceByIndex` бросает `ArgumentOutOfRangeException`, как `SelectEdgeByIndex`);
**дубли индексов отбрасываются** (`Distinct`), чтобы не добавлять грань в `FaceArray` дважды;
`FaceArray()` проверяется на null (`as ksEntityCollection ?? throw`). Возврат
`Create()==FALSE``InvalidOperationException` с пояснением вероятных причин (толщина больше
локального радиуса/толщины стенки; несовместимая топология).
- **Известное ограничение** (документируем, не обрабатываем): на телах со скруглениями/фасками и
на вогнутых/криволинейных гранях оболочка может не построиться (`Create()==FALSE`) даже при
формально корректных входных данных — это ограничение ядра КОМПАС, причину API не сообщает.
## Тестирование
**Integration** (`Category=Integration`, `KompasFixture`, в `SketchPrimitivesTests` или новом
`FeatureOpsTests`): коробка 40×30×20 (`AddRectangle``Extrude`) → `list_faces` → найти
верхнюю грань (нормаль +Z или по площади) → `ShellAsync([верхняя], thickness=2, outward=false)`
→ объём существенно уменьшается (тело стало полым с открытым верхом) и остаётся > 0.
Точная проверка: оболочка 2 мм с открытым верхом = `24000 36×26×18 = 24000 16848 = 7152 мм³`.
Утверждение: `after < before` и `after ∈ (7152·0.95, 7152·1.05)` (≈6794…7510 мм³). Если КОМПАС
считает полость иначе — скорректировать ожидание по фактическому замеру, сохранив узкий допуск.
Unit отдельный не вводим: маппинг `thinType=!outward` тривиален и инлайнится; валидация
параметров — в стиле существующих операций (`extrude`/`revolve` тоже покрыты интеграционно).
## Влияние на документацию
После реализации (через `docs-delegate`): счётчик инструментов +1 (→51), тестов +1;
добавить `shell` в описание формообразующих в `CLAUDE.md`/`README.md`/`docs/ARCHITECTURE.md`/
`docs/presentation.html`; отметить начало пакета B.
@@ -1,137 +0,0 @@
# Дизайн: пакет A — богаче эскизы (примитивы 2D)
**Дата:** 2026-05-26
**Статус:** одобрен к реализации
**Контекст:** kompas3d-mcp — MCP-сервер для КОМПАС-3D через COM API (.NET 8, C#).
## Цель
Расширить набор примитивов эскиза. Сейчас профиль ограничен отрезком, окружностью,
прямоугольником и осевой линией (`sketch_add_line/circle/rectangle/axis`) — без дуг и
кривых недоступна масса реальных профилей. Добавляем **7 инструментов**: дугу (двумя
способами), эллипс, ломаную, правильный многоугольник, сплайн и точку.
Это первый из приоритизированных пакетов расширения MCP (см. анализ функций от 2026-05-26:
пакеты A — эскизы, B — формообразующие, C — массивы, D — параметрика). Выбран пакет A.
## Не входит в объём (YAGNI)
- Параметрические ограничения и размеры эскиза (отдельный крупный пакет).
- Скругление/фаска углов эскиза (`pCorner` у многоугольника не используем).
- Выставление порядка сплайна наружу — фиксируем кубический (порядок 4).
- Дополнительные кривые (`ksArcByPoint`, дуга эллипса, Bezier, trim) — в backlog «пакет A2».
## Сигнатуры COM (Automation — по `docs/Kompas3D_SDK/`; типы — по рефлексии interop)
Все методы — на интерфейсе `ksDocument2D` (API5), вызываются на редакторе открытого
эскиза (`def.BeginEdit()``ksDocument2D`). Углы **в градусах**. Стиль линии `1` =
«основная» (`ksCSNormal`). Каждый метод возвращает `long`: `0` — ошибка.
```
long ksArcBy3Points(double x1,y1, x2,y2, x3,y3, long style)
long ksArcByAngle(double xc, yc, rad, f1_град, f2_град, short direction(1=CCW|-1=CW), long style)
long ksEllipse(LPDISPATCH ksEllipseParam) // interop-свойства: xc,yc,A,B,angle(град),style
long ksRegularPolygon(LPDISPATCH ksRegularPolygonParam, short centre=0)
// count,xc,yc,ang(град),radius,describe(BOOL),style
long ksNurbs(short degree, BOOL close, long style) // degree = ПОРЯДОК (степень+1); 4 = кубический
long ksNurbsPoint(LPDISPATCH ksNurbsPointParam) // x,y,weight=1.0 — на каждый узел
long ksEndObj() // завершить NURBS
long ksPoint(double x, y, long style) // style=0
long ksLineSeg(double x1,y1, x2,y2, long style) // уже используется — для polyline
```
> **Источники истины.** Automation-семантика методов — из `docs/Kompas3D_SDK/`. Точные
> C#-имена/типы свойств — из рефлексии по `libs/kompas-interop/`. Где interop расходится со
> справкой, побеждает interop (компиляция регистрозависима): в частности, `ksEllipseParam`
> имеет свойства **`A`/`B`** с заглавной (в справке — `a`/`b`). NURBS `degree` — это *порядок*
> кривой (степень+1), кубический сплайн = порядок **4**.
**Param-структуры** (`ellipse`/`polygon`/`spline`) берутся через
`KompasObject.GetParamStruct(...)` (`_session.Kompas`), заполняются, передаются в метод и
**освобождаются** `ComHelper.Release` — по дисциплине COM-lifetime проекта. Это новый для
`PartModeler` паттерн (раньше — только прямые методы вроде `ksLineSeg`/`ksCircle`).
Стиль точки: `0` (системный стиль точки, см. SDK `pstyles`).
## Новые MCP-инструменты (группа Sketch)
Контракт инструментов (обёртки в `SketchTools`, как существующие `sketch_add_*`;
возвращают строку-подтверждение, id не возвращают):
| Инструмент | Параметры | COM |
|---|---|---|
| `sketch_add_arc_3points` | `sketchId, x1,y1, x2,y2, x3,y3` | `ksArcBy3Points(..., 1)` |
| `sketch_add_arc` | `sketchId, centerX, centerY, radius, startAngle, endAngle, counterClockwise=true` | `ksArcByAngle(xc,yc,rad,f1,f2,dir,1)` |
| `sketch_add_ellipse` | `sketchId, centerX, centerY, semiMajor, semiMinor, angle=0` | `ksEllipse(param)` |
| `sketch_add_polyline` | `sketchId, points:[{x,y}…], closed=false` | цепочка `ksLineSeg(...,1)` |
| `sketch_add_polygon` | `sketchId, centerX, centerY, vertexCount, radius, inscribed=true, angle=0` | `ksRegularPolygon(param, 0)` |
| `sketch_add_spline` | `sketchId, points:[{x,y}…], closed=false` | `ksNurbs(4,close,1)``ksNurbsPoint`×N → `ksEndObj()` |
| `sketch_add_point` | `sketchId, x, y` | `ksPoint(x,y,0)` |
### Маппинги контракт → COM
- `counterClockwise=true → direction=1`, `false → direction=-1`.
- `inscribed=true → describe=false` (вершины на окружности `radius`);
`inscribed=false → describe=true` (стороны касаются окружности).
- `closed → ksNurbs(close=TRUE)` / для polyline замыкающий `ksLineSeg` от последней к первой.
- Сплайн: `order=4` (кубический), на каждый узел `ksNurbsPoint` с `weight=1.0`; узловой вектор
не задаём (КОМПАС строит автоматически).
### Формат списка точек
`points` — массив объектов `{x, y}` (double). JSON-имена зафиксированы lowercase через
`[JsonPropertyName("x"/"y")]` на record `SketchPoint(double X, double Y)`, чтобы схема
инструмента совпадала с контрактом. Координаты — в плоскости эскиза, мм.
## Валидация (до COM-вызова)
Вынесена в чистые статические guard'ы `SketchGeometry` (unit-тестируемы без COM); бросают
`ArgumentException`/`ArgumentOutOfRangeException`/`ArgumentNullException`:
- `radius > 0`, `semiMajor > 0`, `semiMinor > 0`;
- `vertexCount >= 3`;
- список точек: не `null`, все координаты конечны (`double.IsFinite`), и минимум:
`polyline` — 2 (а при `closed=true` — 3, иначе площадь не образуется); `spline` — 2.
Возврат COM-метода `0` (или `ksEndObj() == 0` для сплайна) → `InvalidOperationException`
с понятным текстом (паттерн `ksLineSeg вернул 0`).
## Структура кода
`PartModeler` (сейчас 443 строки) совмещает эскизы и операции; 7 новых методов раздуют
эскизную часть. Разбиваем на **partial class**, сохраняя публичный класс, DI и общий
реестр id:
- `PartModeler.cs` — поля, реестры `_sketches`/`_features`, helpers (`GetTopPart`,
`RequireSketch`/`RequireOpenSketch`, `SelectEdgeBy*`), `ResetCore`/`Dispose`.
- `PartModeler.Sketch.cs` — создание эскиза (`OpenSketch*`, `CreateSketchOn`), все
примитивы (`AddLine/Circle/Rectangle/Axis` + 7 новых), `CloseSketch*`.
- `PartModeler.Features.cs``ExtrudeAsync`/`RevolveAsync`/`CreateFillet`/`CreateChamfer`/
`RebuildAsync`.
Операции по-прежнему берут `sketch.Entity` из общего `_sketches` — реестр не делится.
Это targeted-улучшение «по ходу работы», контракт не меняется.
## Тестирование
**Unit** (`Category=Unit`, без COM): guard'ы `SketchGeometry` (`RequirePositive`,
`RequireVertexCount`, `RequirePoints` — включая null и не-finite координаты) и маппинги
`ArcDirection` (`counterClockwise→direction`), `PolygonDescribe` (`inscribed→describe`).
(Координат вершин многоугольника вручную не считаем — метод нативный.)
**Integration** (`Category=Integration`, `KompasFixture`): для каждого профилирующего
примитива — открыть эскиз → добавить примитив(ы), образующие замкнутый контур →
`extrude_boss` → проверить правдоподобный объём:
- `polygon`: правильный 6-угольник, `radius=10`, `extrude=5` → объём > 0, согласуется с
площадью правильного шестиугольника.
- `ellipse`: `semiMajor=10, semiMinor=5, extrude=4` → V ≈ π·a·b·h.
- `arc_3points` / `arc`: дуга + замыкающий отрезок (хорда) → замкнутый сегмент → V > 0.
- `polyline`: треугольник из 3 точек, `closed=true` → V > 0.
- `spline`: замкнутый сплайн по 4+ точкам → V > 0.
- `point`: вызов не падает, эскиз остаётся валидным (точка контур не образует — без
проверки объёма).
## Влияние на документацию
После реализации обновить (через навык `docs-delegate`): счётчики инструментов/тестов и
перечни в `CLAUDE.md`, `README.md`, `docs/ARCHITECTURE.md`; пометить пакет A выполненным в
анализе пробелов. Память `kompas-step-and-assembly-api` не затрагивается.
@@ -1,55 +0,0 @@
# Дизайн: пакет B (шаг 3) — кинематическая операция (sweep)
**Дата:** 2026-05-26
**Статус:** на ревью (автономный режим; ревьюер — Codex)
## Цель
Добавить формообразующую **кинематическую операцию** (sweep) — переместить профиль вдоль
траектории, образуя тело. Третий шаг пакета B. Один MCP-инструмент `sweep`. В отличие от
loft (требует параллельные сечения → offset-плоскость, пакет E), sweep естественно работает с
базовыми плоскостями: профиль на одной, траектория на перпендикулярной.
## Сигнатуры COM (рефлексия interop)
```
Obj3dType.o3d_baseEvolution = 45
ksBaseEvolutionDefinition:
short sketchShiftType // тип переноса сечения вдоль пути
bool SetSketch(object) // профиль (сечение)
object PathPartArray() // ksEntityCollection сегментов траектории
bool SetThinParam(bool thin, short thinType, double normal, double reverse)
```
**Паттерн:** `NewEntity(o3d_baseEvolution)``GetDefinition()``SetSketch(профиль)`
`PathPartArray().Add(траектория)``sketchShiftType``SetThinParam(false,…)` (сплошное) →
`Create()`.
## Новый MCP-инструмент (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `sweep` | `profileSketchId: int`, `pathSketchId: int` | Кинематическая операция: переместить замкнутый профиль `profileSketchId` вдоль траектории `pathSketchId`, образуя сплошное тело. Профиль и траектория — на разных (обычно перпендикулярных) плоскостях; начало траектории совпадает с плоскостью профиля. Возвращает id операции. |
**Решения (YAGNI):** тонкостенный режим (`thin`) и `sketchShiftType` наружу не выставляем —
сплошное тело, `sketchShiftType=0` (перенос с сохранением угла). Можно расширить позже.
## Реализация
- **Метод** `SweepAsync(int profileSketchId, int pathSketchId, CancellationToken)` в
`PartModeler.Features.cs`. Оба эскиза закрываются перед операцией (`CloseSketchCore`);
берутся `sketch.Entity` из реестра. `profileSketchId == pathSketchId``ArgumentException`.
Возвращает id операции.
- **Инструмент** `sweep` в `FeatureTools.cs`.
- `PathPartArray()` проверяется на null (`as ksEntityCollection ?? throw`).
- Транзитный `ksBaseEvolutionDefinition` не освобождается — консистентно (долг v2-2).
- `Create()==FALSE``InvalidOperationException` (профиль/траектория несовместимы: начало пути
не на плоскости профиля, самопересечение?).
## Тестирование
**Integration** (`FeatureOpsTests`): профиль — окружность R5 на XOY (центр 0,0); траектория —
вертикальный отрезок на XOZ `(0,0)→(0,50)` (начинается в центре профиля, идёт по +Z) →
`SweepAsync(profile, path)` → цилиндр R5×H50, объём `π·25·50 ≈ 3927 мм³`. Проверка
`InRange(volume, 3927·0.95, 3927·1.05)`. Если `sketchShiftType=0` не строит — перебрать тип
(0/1) по факту, сохранив проверку объёма.
@@ -1,159 +0,0 @@
# Дизайн: вставка компонента в сборку (assembly_add_component) через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (3 интеграционных теста зелёные, 136 всего); ревью Codex спека учтено (8 замечаний)
## Эмпирическая находка при реализации (важно — не разучивать)
`part.UpdatePlacement(true)` возвращает **FALSE** при перемещении вручную позиционируемого
компонента (без сопряжений) — это **НЕ ошибка** (нечего пересчитывать от зависимостей). Само
перемещение задаёт `placement.SetOrigin(x,y,z)` (живой объект Placement, не копия — проверено:
сдвиг origin на +50 даёт габарит сборки X[50,90]), а **видимым в геометрии** оно становится после
`top.RebuildModel(true)`. Поэтому: результат `SetOrigin` проверяем (FALSE = ошибка), результат
`UpdatePlacement` **игнорируем**, обязателен `RebuildModel(true)`. Разфиксация первого компонента
(`Fixed=false`) **НЕ нужна** — зафиксированный КОМПАСом первый компонент всё равно перемещается
через `SetOrigin`+`RebuildModel` (проверено: оба теста — origin и offset — зелёные без снятия
фиксации).
## Цель
Открыть новый класс функционала — **СБОРКИ**. До сих пор сделана только геометрия ДЕТАЛИ.
Первый инкремент вехи: **вставить деталь из сохранённого `.m3d`-файла в активную сборку** с
заданием положения. Это фундамент — без вставки компонентов сборка пуста.
`list_components` (чтение сборки) уже есть; теперь учимся её НАПОЛНЯТЬ.
## Архитектурное решение
Сборки — это **НЕ `PartModeler`** (он строит API5-деталь `ksPart`). Заводим **новый сервис
`AssemblyService`** (API7, паттерн как у `HoleService`/`FaceEditService`: контейнеры берём
COM-QI, операции на STA-потоке `KompasDispatcher`) и **новый класс инструментов `AssemblyTools`**.
- `src/Kompas.Mcp.Core/Assemblies/AssemblyService.cs` — сервис.
- `src/Kompas.Mcp.Core/Assemblies/AssemblyValidation.cs` — чистые static-валидаторы (unit-тест).
- `src/Kompas.Mcp.Core/Assemblies/AddedComponent.cs` — record результата (Index, Name).
- `src/Kompas.Mcp.Host/Tools/AssemblyTools.cs` — MCP-инструмент.
- DI: `AddSingleton<AssemblyService>()` в `Program.cs`.
(Namespace `Kompas.Mcp.Core.Assemblies` — во множественном числе, чтобы не конфликтовать с
`System.Reflection.Assembly`.)
## Подтверждённый workflow (рефлексия interop + справка)
Рефлексией `KompasAPI7.dll` подтверждено:
- `IParts7.AddFromFile(string FileName, bool ExternalFile, bool Redraw)``Part7`.
- `IPart7.Placement` (`Placement3D`), `IPart7.UpdatePlacement(bool Redraw)`.
- `IPlacement3D.SetOrigin(double X, double Y, double Z)`.
```
// RequireActiveAssembly (см. ниже) — раздельные проверки до доступа к TopPart:
IKompasDocument doc = app.ActiveDocument; // != null
// doc.DocumentType == ksDocumentAssembly (деталь тоже IKompasDocument3D — отсечь по типу!)
IKompasDocument3D doc3d = (IKompasDocument3D)doc;
IPart7 top = (IPart7)doc3d.TopPart; // != null
IParts7 parts = top.Parts;
IPart7 part = parts.AddFromFile(filePath, /*ExternalFile*/ true, /*Redraw*/ true) as IPart7
?? throw; // проверяем результат
// ExternalFile=true: компонент — ссылка на внешний .m3d (что нам и нужно — отдельный файл-источник).
// Redraw=true: вставить и перерисовать ИСХОДНОЕ положение (в начало координат сборки).
IPlacement3D pl = part.Placement as IPlacement3D ?? throw; // read-only свойство, мутируем
if (!pl.SetOrigin(x, y, z)) throw; // мировые координаты сборки, мм
if (!part.UpdatePlacement(/*Redraw*/ true)) throw; // именно это ПРИМЕНЯЕТ перемещение
```
**Проверки результатов (как в `HoleService`):** `AddFromFile` приводится к `IPart7` (иначе ошибка);
`Placement` приводится к `IPlacement3D`; `SetOrigin(...)==true`; `UpdatePlacement(true)==true`.
Неуспех любого — `InvalidOperationException` с понятным текстом. (Откат «осиротевшего» компонента
при неуспехе позиционирования — best-effort через `part.Owner?.Delete()`; на практике origin-сдвиг
надёжен, но проверки обязательны.)
**Redraw ≠ применение перемещения (важно):** `AddFromFile(..., Redraw:true)` вставляет и
перерисовывает компонент в ИСХОДНОМ положении. Само перемещение применяет и проверяет именно
`part.UpdatePlacement(true)` — не `Redraw`. Отдельный `doc3d.RebuildDocument()` в базовом workflow
не нужен (интеграционный тест подтверждает габарит после `UpdatePlacement`).
**Почему `AddFromFile`, а не `AddFromFileWithParam`:** для первого инкремента нужен только сдвиг
origin. `AddFromFile` + `Placement.SetOrigin` + `UpdatePlacement` — минимальный надёжный путь
(подтверждён рефлексией: все три члена есть в interop). `AddFromFileWithParam` требует получить
`IPlacement3D`/`IInsertPartParameters` через `IKompasDocument1.GetInterface(код)` до вставки — это
лишняя неочевидность (коды 11223/13196 не проверены рефлексией) и YAGNI на этом шаге. Ориентацию
(`SetVector`/углы Эйлера) и параметры вставки добавим в следующих инкрементах при необходимости.
**Единицы:** `x/y/z` — миллиметры в мировой СК сборки. По умолчанию (0,0,0) компонент встаёт в
начало координат сборки.
**Фиксация:** через API первый компонент НЕ фиксируется автоматически. На этом шаге фиксацию НЕ
выставляем (YAGNI) — для теста объёмом/габаритом она не нужна; добавим параметр `fixed` позже,
если понадобится.
## MCP-инструмент (новая группа Assembly)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `assembly_add_component` | `filePath: string`, `x=0`, `y=0`, `z=0` | Вставить деталь/подсборку из `.m3d`/`.a3d`-файла в активную сборку; origin компонента — (x,y,z) мм. Возвращает индекс и имя вставленного компонента. |
Контракт: активный документ должен быть **сборкой** (иначе понятная ошибка); файл должен
существовать. Возврат — текст вида «Компонент '<имя>' вставлен в сборку, индекс N, позиция (x,y,z)».
## Валидация (чистые static, unit-тест)
`AssemblyValidation`:
- `NormalizeComponentPath(string? path) → string``Trim` → проверка непустоты → проверка
расширения (`.m3d`/`.a3d`, case-insensitive; иначе `ArgumentException`) → `Path.GetFullPath`
(вернуть абсолютный путь). Чистая (не трогает ФС) → unit-тест.
- `RequireFinitePosition(double x, double y, double z)` — координаты конечны (`double.IsFinite`;
отсекает `Infinity`/`NaN`, как в `HoleService`).
Проверка существования файла (`File.Exists` на нормализованном пути) — в сервисе (не чистая, не
unit-тест), даёт `FileNotFoundException` с абсолютным путём.
## Проверка активного документа (helper в сервисе)
`RequireActiveAssembly()` — раздельные ошибки (Codex #2):
1. `ActiveDocument == null` → «Нет активного документа».
2. `doc.DocumentType != ksDocumentAssembly` → «Активный документ не сборка (тип: …). Создайте
сборку: document_create assembly». (Деталь — тоже `IKompasDocument3D`, поэтому проверка по типу
обязательна ДО доступа к `TopPart`.)
3. `doc is not IKompasDocument3D` → «Активный документ не 3D».
4. `doc3d.TopPart is not IPart7` → «Не удалось получить TopPart сборки».
## Реализация
- `AssemblyService.AddComponentAsync(string filePath, double x, double y, double z, ct)`
на STA-потоке: валидация → проверка типа активного документа (сборка) → `AddFromFile`
`Placement.SetOrigin``UpdatePlacement` → вернуть `AddedComponent(Index, Name)`.
Индекс = `parts.Count - 1` после вставки; имя = `part.Name`.
- Транзитные RCW не освобождаем точечно — консистентно с остальным слоем (долг v2-2).
## Тестирование
### Unit (`AssemblyValidationTests`)
- `NormalizeComponentPath` бросает на null/пустой/пробельный; бросает на чужом расширении
(`.step`/`.txt`); пропускает `.m3d`/`.a3d` (в т.ч. с пробелами и в разном регистре), возвращает
абсолютный путь.
- `RequireFinitePosition` бросает на NaN/Infinity по каждой координате; пропускает конечные.
### Integration (`AssemblyTests`, Collection KompasCollection)
Паттерн: построить деталь → `SaveAsAsync(.scratch/*.m3d)`**проверить `File.Exists` + размер>0**
`CloseAsync` → создать сборку → `AddComponentAsync` → проверить → `finally CloseAsync`.
**Геометрия детали фиксирована от нуля** (эскиз 0..40 × 0..40, выдавить 0..20 ⇒ локальный габарит
X[0,40] Y[0,40] Z[0,20]) — чтобы абсолютные ожидания по габариту были детерминированы (Codex #7).
1. **Вставка в начало координат:** сохранить деталь-коробку, проверить файл, закрыть. Создать
сборку. `before = list_components.Count`. `var c = AddComponentAsync(file, 0,0,0)`.
- `list_components.Count == before + 1` (Codex #6); компонент по индексу `c.Index`:
`SizeX≈40, SizeY≈40, SizeZ≈20` (±допуск); `c.Name` непуст.
- `get_bounding_box` сборки: X∈[0,40], Y∈[0,40], Z∈[0,20] (±допуск).
2. **Позиционирование:** та же деталь, `AddComponentAsync(file, 50, 0, 0)`.
- `get_bounding_box` сборки: X∈[50,90] (сдвиг origin на +50 подтверждает работу позиции).
Объёмом сборку тоже можно проверить (`get_part_info` на TopPart агрегирует компоненты) —
но габарит надёжнее и прямо проверяет позицию. Артефакты `.m3d` — в gitignored `.scratch/`.
## Дальнейшие инкременты (вне этого спека)
2. `assembly_add_mate` — сопряжения (`IMateConstraints3D.Add(mc_Coincidence/mc_Distance/…)` +
`BaseObject1/2` + `Update`). Типы: совпадение/параллельность/перпендикулярность/касательность/
концентричность/расстояние/угол/симметрия.
3. Перемещение/массив/фиксация компонентов — по необходимости.
@@ -1,138 +0,0 @@
# Дизайн: сопряжение компонентов сборки (assembly_add_mate) через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (2 интеграционных + 14 unit, всего 154); ревью Codex спека (8) и реализации (9) учтено
## Правки по ревью реализации Codex (9 замечаний)
- **#1 `Valid==false` → ошибка** (а не «успех с valid=false»): после Update+Rebuild проверяем
`mate.Valid`, иначе бросаем (срабатывает откат). `AddedMate.Valid` теперь всегда true при успехе.
Это же косвенно ловит грани одного компонента (#5) — мат выродится.
- **#6/#7 distance `value` > 0** (не ≥0): нулевой зазор вырождается в coincidence →
`RequireMateValue` требует строго `> 0`; unit-кейс `value=0` бросает.
- **#8 негативный интеграционный тест**: точка в пустоте → грань не найдена → ошибка.
- **#2 откат best-effort** оставлен намеренно (исходное исключение важнее ошибки отката; паттерн
`HoleService`). **#3** `mates.Add ?? throw` до try корректно (null = ничего не добавлено).
**#4** «первый IFace» оставлен (консистентно с HoleService/move_face). **#9** точечный release
RCW отклонён (конвенция v2-2).
## Решения по ревью Codex (важные правки к дизайну ниже)
- **Scope урезан до `coincidence` + `distance`** (#6, #5): только эти два типа геометрически
подтверждены спайком → только они в публичном `MateType`/`Parse`. Остальные (parallel/
perpendicular/concentric/angle/tangency) и `Alignment`-параметр — будущие инкременты с тестами.
- **Валидация value по типу** (#1): `Mates.RequiresValue` → distance требует `value` конечный и
`≥ 0`; для типов без value параметр игнорируется (дефолт 0). Валидатор
`AssemblyValidation.RequireMateValue(requiresValue, value)`.
- **Полный откат** (#3): весь участок после `mates.Add` — в try/catch; при ЛЮБОЙ ошибке (BaseObject,
ParamValue, Update=FALSE, RebuildModel=FALSE) сопряжение удаляется через `mate.Owner?.Delete()`
(`IFeature7`, как у компонента в инкременте 1).
- **Возврат результата** (#8b): `AddedMate { Type: string, Valid: bool }` — для отчёта инструмента и
ассертов теста (`Valid`).
- **Усиленный тест** (#2): явно `bbBefore ≈ 120`; после mate — `Valid==true`, размеры каждого
компонента (`SizeX≈20`) неизменны, ширина сборки изменилась.
- **RCW** (#7): точечный release НЕ вводим (конвенция v2-2, как в инкременте 1/HoleService); все
COM-вызовы — внутри `dispatcher.InvokeAsync` (уже так). Долг зафиксирован в OPEN_QUESTIONS v2-2.
- **Выбор грани** (#8a): паттерн «первый IFace» (как в HoleService/move_face); в описании
инструмента — «точка в СЕРЕДИНЕ грани» (на ребре/углу хит неоднозначен).
## Цель
Веха СБОРКИ, инкремент 2. После вставки компонентов (`assembly_add_component`, инкремент 1) —
**сопряжения**: наложить связь между гранями двух компонентов (совпадение / на расстоянии),
чтобы решатель сборки спозиционировал компоненты относительно друг друга. Это вторая
половина минимально полезного цикла сборки (вставить → связать).
## Спайк: механизм подтверждён вживую (НЕ разучивать)
Спайк (`AssemblyMateSpikeTests`, оба теста зелёные) подтвердил полный путь API7:
```
IPart7 top = (IPart7)doc3d.TopPart; // активная сборка
IMateConstraints3D mates = top.MateConstraints; // COM-свойство (не QI)
IMateConstraint3D mate = mates.Add(MateConstraintType); // напр. mc_Coincidence=0 / mc_Distance=5
mate.BaseObject1 = (IModelObject)face1; // грань компонента 1
mate.BaseObject2 = (IModelObject)face2; // грань компонента 2
if (distance|angle) mate.ParamValue = value; // зазор (мм) / угол (°)
bool ok = mate.Update(); // наложить сопряжение
top.RebuildModel(true); // применить к геометрии
```
**Выбор граней компонентов:** `top.FindObjectsByPoint(x, y, z, FirstLevel=false)``FirstLevel=false`
заставляет спуститься В компоненты и вернуть их грань в **контексте сборки** (мировые координаты с
учётом placement компонента). Это именно тот `IModelObject`, который нужен решателю сопряжений.
**Проверенные результаты (2 куба 20³, A в нуле x[0,20], B со сдвигом +100 x[100,120]):**
- **Совпадение** (`mc_Coincidence`) +X грани A (20,10,10) с X гранью B (100,10,10): `Update=True`,
`Valid=True`, `Alignment=ksMCAlignmentClosest` (дефолт). Грани сошлись заподлицо → ширина сборки
по X **120 → 40** (кубы соприкасаются). Решатель подвинул A (к B), не B.
- **Расстояние** (`mc_Distance`, `ParamValue=30`) тех же граней: `Update=True`, `Valid=True`. Грани
встали в 30 мм → ширина сборки по X **120 → 70** (20+30+20).
**Тонкости (эмпирика):**
- `Alignment` НЕ выставляем — дефолт работает (`ksMCAlignmentClosest` для совпадения,
`ksMCAlignmentUnknown` для расстояния). Параметр выравнивания — расширение позже (YAGNI).
- Решатель может двигать **любой** компонент (в т.ч. первый, «зафиксированный»). Поэтому результат
проверяем по **ширине габарита** (грани сблизились), а не по тому, какой компонент сдвинулся.
- `mates.Add` возвращает `IMateConstraint3D`; результат `Update()` проверяем (FALSE = не наложилось).
После — `RebuildModel(true)` (результат проверяем, как в инкременте 1).
## Архитектура
Метод в существующем `AssemblyService` (он уже про сборки). Свой enum `MateType` (паттерн
`BasePlane`/`CoordinateAxis`) маппится на КОМПАС `MateConstraintType` — COM-константы не текут в
Host/тесты.
- `src/Kompas.Mcp.Core/Assemblies/MateType.cs` — enum `MateType` + static `Mates`
(`ToConstraintType`, `Parse(string)` англ./рус., `RequiresValue`).
- `AssemblyService.AddMateAsync(MateType, x1,y1,z1, x2,y2,z2, value, ct)` — реализация.
- Инструмент `assembly_add_mate` в `AssemblyTools` (тип сопряжения строкой → `Mates.Parse`).
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `assembly_add_mate` | `mateType: string`, `x1,y1,z1`, `x2,y2,z2`, `value=0` | Наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам (x1..z1) и (x2..z2). `mateType`: coincidence / distance / parallel / perpendicular / concentric / angle / tangency. Для distance — `value` зазор (мм), для angle — `value` угол (°). |
Контракт: активный документ — сборка (`RequireActiveAssembly`, уже есть); обе точки должны попасть
на грани компонентов. **Гарантированно валидированы геометрией: coincidence, distance.** Остальные
типы маппятся и вызываемы через тот же путь Add+BaseObject+Update, но не покрыты регрессионным
тестом по габариту (требуют иной геометрии/проверки) — помечены в описании инструмента.
## Валидация (чистые static, unit-тест)
`Mates`:
- `Parse(string)` — англ./рус. → `MateType`; иначе `ArgumentException` со списком допустимых.
- `ToConstraintType(MateType)` — → `MateConstraintType`.
- `RequiresValue(MateType)` — true для Distance/Angle.
В сервисе: `RequireFinitePosition` для обеих точек (уже есть в `AssemblyValidation`); если
`RequiresValue` — проверить `double.IsFinite(value)` (новый чистый валидатор
`AssemblyValidation.RequireFiniteValue`).
## Реализация
`AddMateCore`: валидация точек (и value, если нужен) → `RequireActiveAssembly`
`FindFaceInAssembly` (FirstLevel=false) для обеих точек (понятная ошибка, если грань не найдена) →
`mates.Add(Mates.ToConstraintType(type))``BaseObject1/2``ParamValue` (если нужен) →
`Update()` (FALSE → откат сопряжения через `IFeature7.Delete` + ошибка) → `RebuildModel(true)`
(FALSE → ошибка). Транзитные RCW не освобождаем точечно (долг v2-2, как везде).
## Тестирование
### Unit (`MateTests` или в `AssemblyValidationTests`)
- `Mates.Parse` — англ. и рус. синонимы → верный `MateType`; бросает на мусоре.
- `Mates.ToConstraintType` — каждый `MateType` → ожидаемый `MateConstraintType`.
- `Mates.RequiresValue` — true для Distance/Angle, false для прочих.
- `AssemblyValidation.RequireFiniteValue` — бросает на NaN/Infinity.
### Integration (`AssemblyMateTests`)
Паттерн: построить куб 20³ → сохранить → создать сборку → вставить 2 компонента (A в нуле, B +100)
→ сопряжение → проверить ширину габарита → `finally CloseAsync`.
1. **Совпадение** граней A(+X)/B(−X): ширина по X **120 → ~40** (соприкосновение).
2. **Расстояние 30**: ширина по X **120 → ~70** (зазор 30).
(Эти два сценария = промотированный спайк; спайк-файл удаляется.)
## Дальнейшее (вне спека)
- Выравнивание (`Alignment`), типы concentric/angle/parallel с геометрической проверкой.
- Перемещение/фиксация/массив компонентов.
@@ -1,67 +0,0 @@
# Дизайн: уклон граней (draft / incline) через API5
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (тест зелёный); на ревью (ревьюер — Codex)
## Цель
Добавить операцию **«Уклон»** (draft) — наклонить грани на угол относительно нейтральной
(опорной) плоскости. Приоритет 1 текущей сессии.
## Ключевой факт: уклон ЕСТЬ в API5 (вопреки прежнему предположению)
Память `kompas-formative-features-api5` ошибочно считала draft недоступным в API5 — она искала
тип со словом **Draft**, но операция называется **«Уклон» = Incline**. Рефлексия подтвердила:
`Obj3dType.o3d_incline=42` + **`ksInclineDefinition`** есть в `Kompas6API5.dll`. Это стандартный
API5-паттерн (как shell/rib/fillet) — НЕ нужен API7. Плюс: операция регистрируется в реестре
`_features`, значит массивы/зеркало пакета C к ней применимы.
> `o3d_DraftFromEdges=644` (API7 `IDraftFromEdges`) — это ДРУГАЯ операция «уклон от базовой линии»
> (v22), без нейтральной плоскости. Для обычного уклона граней используем `IIncline`/`ksInclineDefinition`.
## Сигнатуры (рефлексия interop + справка)
```
Obj3dType.o3d_incline = 42
ksInclineDefinition:
double angle // угол уклона (градусы)
bool direction // ЭМПИРИЧЕСКИ: false=расширение, true=сужение (обратно справке!)
object FaceArray() // ksEntityCollection уклоняемых граней (Add по одной)
bool SetPlane(object) // нейтральная (опорная) плоскость — ksEntity
object GetPlane()
```
**Паттерн:** `NewEntity(o3d_incline)``GetDefinition()``FaceArray().Add(face)` (по граням) →
`SetPlane(нейтральная)``angle``direction``Create()` → регистрация в `_features`.
**Нейтральная плоскость** — сечение тела в ней остаётся неизменным; грани уклоняются вокруг
линии пересечения с ней. На первом этапе — базовая координатная плоскость (XOY/XOZ/YOZ).
**Направление (важно):** в interop `direction=false` даёт **расширение**, `true`**сужение**
(проверено объёмом; обратно тексту справки). Контракт инструмента: `outward` (true=расширение),
маппинг `def.direction = !outward`.
## MCP-инструмент (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `draft` | `faceIndices: int[]`, `neutralPlane: "XOY"\|"XOZ"\|"YOZ"`, `angle: double`, `outward=false` | Уклон граней `faceIndices` (из list_faces) на `angle`° относительно нейтральной координатной плоскости. `outward` — расширение/сужение. Возвращает id. |
**Решения (YAGNI):** нейтральная плоскость — только координатная (как mirror_operation); уклон от
ребра (`IDraftFromEdges`) — отдельная задача позже. Грани — по стабильному индексу из list_faces.
## Реализация
- **Метод** `DraftAsync(IReadOnlyList<int> faceIndices, BasePlane neutralPlane, double angle, bool outward, ct)`
в `PartModeler.Features.cs`. Валидация: `angle` конечен и в (0; 90); `faceIndices` не пуст,
`Distinct`; диапазон всех индексов проверяется до мутации (`SelectFaceByIndex`).
- **Инструмент** `draft` в `FeatureTools.cs` (`neutralPlane` парсится `BasePlanes.Parse`).
- Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
- `Create()==FALSE``InvalidOperationException` (грань не граничит с нейтральной плоскостью?).
## Тестирование (Integration, `FeatureOpsTests`)
**Уклон**: коробка 40×40×20 (низ на Z=0) → 4 боковые грани (площадь 800) под уклон 10° внутрь
(`outward=false`) относительно XOY → усечённая пирамида: верх ≈ 402·20·tan10°=32.95;
`V = (20/3)(1600+1085.5+√(1600·1085.5)) ≈ 26689` мм³; `InRange(after, ±10%)` и `after < before`. ✓
Боковые грани выбираются из `ListFaces` по площади 800 (тип plane).
@@ -1,132 +0,0 @@
# Дизайн: ассоциативная привязка диаметрального и радиального размеров к геометрии вида (API7)
**Дата:** 2026-05-27
**Статус:** дизайн согласован (вариант A — флаг `associate`), спайк проведён, ревью Codex спека учтено — к реализации
## Правки по ревью Codex (спек)
- **#3/#4** объём сужен до **окружностей** (`IDrawingContainer.Circles`): спайк подтвердил привязку
только к окружности. Дуги (`Arcs`) — следующий инкремент со своим спайком (привязка `IArc` как
`BaseObject` для диам./радиального не проверена; дуги одного родителя неотличимы по центру+радиусу).
- **#1** вид резолвится один раз и приводится к обоим контейнерам: `ISymbols2DContainer` (создание
размера) И `IDrawingContainer` (поиск окружности). Новый helper `RequireViewContainers(viewNumber)`
`(IView, ISymbols2DContainer, IDrawingContainer)`.
- **#2** весь блок после `…Dimensions.Add()` (присвоение `BaseObject`/`Angle`/`AutoNominalValue`,
`Update`, `Valid`, read-back) — внутри того же `try/catch` с `Delete` при любом сбое (как свободный путь).
- **#5** тест доказывает ассоциативность: ключ поиска `radius=10.3` (в допуске находит окружность R10)
→ associate вернёт Ø=20 (из геометрии), свободный дал бы 2·10.3=20.6; `Value≈20` (не 20.6) доказывает
чтение из геометрии. + диагностический read-back `BaseObject != null`.
- **#6** обе ветки (свободная/associate) конвертируют `angle` через `DimensionAngles.ToRadians(angleDeg)`
перед присвоением `dim.Angle`.
- **#7** пост-инвариант значения (`value > 0 && double.IsFinite`) сохраняется и в associate-ветке (иначе откат).
- **#8** регресс-тесты свободного пути (`associate=false`) для ОБОИХ — диаметрального и радиального.
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 7. Размеры пока «свободные» (значение из заданных координат). Ассоциативная
привязка делает диаметральный/радиальный размер **связанным с проекцией геометрии**: значение (Ø/R)
читается из спроецированной окружности/дуги вида через `BaseObject` и обновляется вместе с моделью.
Первый шаг к «умному» чертежу.
## Объём (обоснован рефлексией interop)
Только **диаметральный** и **радиальный**, привязка только к **окружности** (`IDrawingContainer.Circles`)
у `IDiametralDimension`/`IRadialDimension` есть свойство `BaseObject`; `_Circle` реализует
`IDrawingObject`, поэтому объект из коллекции присваивается `dim.BaseObject` (подтверждено спайком).
У `ILineDimension` `BaseObject` НЕТ; угловой требует `BaseObject1/2`; привязка к дуге (`Arcs`) не
проверена — всё это отдельные инкременты (вне объёма).
## Спайк: механизм подтверждён вживую (НЕ разучивать)
Спайк (`_SpikeAssocDim`, прогнан на реальном КОМПАС, затем удалён): цилиндр (окружность R10, выдавлен
на 20) → стандартные виды. Один из видов содержит спроецированную окружность.
```
IDrawingContainer dc = (IDrawingContainer)view; // QI от вида (как ISymbols2DContainer)
ICircles circles = dc.Circles; // Count=1; circles[0] as ICircle → Xc=0,Yc=0,R=10
IDiametralDimension dia = symbols.DiametralDimensions.Add();
dia.BaseObject = circles[0]; // IDrawingObject; Xc/Yc/Radius НЕ задаём
dia.Angle = π/4; // направление выноски (размещение)
dia.Update(); // True, Valid=True
double v = ((IDimensionText)dia).NominalValue; // == 20 — ДИАМЕТР, прочитан из геометрии!
```
**Проверено:** `BaseObject=circle` без задания координат → `Update=True`, `Valid=True`,
диаметральный `NominalValue=20` (=2·R геометрии), радиальный `NominalValue=10` (=R). `BaseObject`
остаётся непустым после. Геометрия вида: `IDrawingContainer.Circles` (`ICircle`: `Xc/Yc/Radius`),
`.Arcs` (`IArc`: `Xc/Yc/Radius/Angle1/Angle2`), `.LineSegments`. **Нет** «find by point» — выбор
перебором коллекции.
## MCP-инструменты (расширение существующих, вариант A)
К `drawing_add_diametral_dimension` и `drawing_add_radial_dimension` добавляется параметр
`associate=false`:
| `associate` | Поведение |
|---|---|
| `false` (по умолчанию) | Текущее «свободное»: `Xc/Yc/Radius` задаются напрямую (без изменений, обратная совместимость). |
| `true` | `(xc,yc,radius)`**ключ поиска**: найти в виде окружность/дугу с центром ≈ (xc,yc) и радиусом ≈ radius, привязать `BaseObject`; значение (Ø/R) читается из геометрии. |
Координаты ключа — view-local (= координаты модели при масштабе 1:1; при ином масштабе — масштабированные,
как у прочих размеров). `angle` — направление выноски (°), как сейчас. `viewNumber` — как сейчас.
## Выбор объекта
В `IDrawingContainer` нет «find by point». Перебираем `Circles`, собираем кандидатов `(Xc, Yc, Radius)`,
ищем подходящий по ключу `(xc, yc, radius)`:
- кандидат подходит, если `dist(центр, ключ) ≤ tol` И `|R radius| ≤ tol` (`tol = 1.0 мм`, константа);
- ровно 1 подходящий → привязываем; 0 → ошибка «окружность не найдена рядом с (xc,yc) R≈radius»;
>1 → ошибка «неоднозначно — уточните координаты/радиус».
Радиус в ключе снимает неоднозначность концентрических окружностей (цековка R5/R10 — ключ R10
выберет только внешнюю). Логика отбора — чистая (unit-тест), перечисление COM — в сервисе.
## Архитектура
- `DrawingService.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angleDeg, associate, ct)` и
`AddRadialDimensionAsync(..., associate, ct)` — добавлен `associate` (по умолчанию `false`). При `false`
текущая ветка без изменений. Обе ветки: `angleRad = DimensionAngles.ToRadians(angleDeg)`.
- При `associate=true`: `RequireViewContainers(viewNumber)` → собрать кандидатов из `Circles`
(`CollectCircles`) → `CircularObjectMatch.SelectMatchIndex(...)` → взять `IDrawingObject` по индексу →
`symbols.{Diametral|Radial}Dimensions.Add()`**внутри `try/catch`+`Delete`**: `dim.BaseObject = obj`,
`dim.Angle = angleRad`, `AutoNominalValue=true``Update()` (FALSE→откат) → `Valid` (false→откат) →
`value = NominalValue` (из геометрии); `value<=0 || !IsFinite` → откат → `DrawingDimensionResult`.
- Новый helper `RequireViewContainers(viewNumber)` → `(IView View, ISymbols2DContainer Symbols,
IDrawingContainer Drawing)` — резолвит вид один раз, оба QI с раздельной диагностикой (переиспользует
`FindView`).
- Новый файл `src/Kompas.Mcp.Core/Drawings/CircularObjectMatch.cs`: чистый статический
`record CircleCandidate(double Xc, double Yc, double Radius)` + `int SelectMatchIndex(IReadOnlyList<CircleCandidate>
candidates, double keyX, double keyY, double keyR, double tol)` — возвращает индекс единственного
совпадения; `InvalidOperationException` при 0 (нет) / >1 (неоднозначно).
- Сбор кандидатов из COM (Circles в список с сохранением ссылки на `IDrawingObject` + геометрии) — в
сервисе (private helper `CollectCircles(IDrawingContainer)` → `List<(IDrawingObject Obj, CircleCandidate Geom)>`).
- `DrawingDimensionResult` переиспользуется (значение из геометрии). Свободный путь не трогаем.
## Валидация
- Оба пути: `RequireFiniteCoords(xc, yc, angleDeg)` + `RequirePositiveRadius(radius)` (есть). В
associate-режиме radius — положительный ключ поиска.
- `CircularObjectMatch.SelectMatchIndex` — чистая логика отбора (unit).
## Тестирование
### Unit (`CircularObjectMatchTests`)
- Единственное совпадение по центру+радиусу → индекс.
- Нет кандидатов в допуске → `InvalidOperationException` (нет).
- Два совпадения в допуске → `InvalidOperationException` (неоднозначно).
- Концентрические (один центр, разный R): ключ по R выбирает нужный (не неоднозначно).
- Пустой список → `InvalidOperationException`.
### Integration (`DrawingAssociativeDimensionTests`; модель — цилиндр R10, как в спайке)
1. **Диаметральный associative + доказательство геометрии**: ключ `(0,0,10.3)` (radius в допуске
находит окружность R10) → `Value ≈ 20` (Ø из геометрии, НЕ 2·10.3=20.6 — доказывает чтение из
геометрии); попадание в вид (`DiametralDimensions.Count` +1).
2. **Радиальный associative**: `(0,0,10.3)` → `Value ≈ 10` (R из геометрии, не 10.3).
3. **Нет окружности рядом**: ключ далеко от геометрии → `InvalidOperationException`; счётчик не вырос (откат).
4. **Свободный путь не сломан** — диаметральный `associate=false` `(xc,yc,15)` → `Value ≈ 30`.
5. **Свободный путь не сломан** — радиальный `associate=false` `(xc,yc,12)` → `Value ≈ 12` (регресс обоих).
6. **Нет видов** (associate) → понятная ошибка.
## Дальнейшее (вне спека)
- Ассоциативная привязка углового (`BaseObject1/2` — пара отрезков) и шероховатости (`IRough.BaseObject`);
линейный — `BaseObject` отсутствует (нужен иной механизм). Рамка/формат листа; выноски/базы/допуски формы.
@@ -1,108 +0,0 @@
# Дизайн: диаметральный размер на виде чертежа (drawing_add_diametral_dimension) через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (5 интеграционных + unit, всего 218); ревью Codex спека и реализации учтено
## Правки по ревью реализации Codex
- **#2** инвариант диаметра: пост-проверка `value <= 0 || !double.IsFinite(value)` (а не `Abs<1e-6`).
- **#3** сообщение `drawing_create_standard_views` обобщено: «номера видов для инструментов размеров».
- **#4a** `DimensionAngles.ToRadians` — проверка конечности результата (защита от переполнения).
- **#5d** тест `viewNumber: 999` (виды есть, номер не найден) — покрывает ветку FindView.
- **#6** комментарий `DimensionType` переформулирован (просто COM-дефолт, без претензии на «стиль»).
- **#4b** минимальный радиус — отклонён (`>0` честный контракт). **#5a/#5b/#5c** (реальная окружность,
read-back угла, пост-Add откат) — отклонены (инструмент geometry-agnostic; ToRadians покрыт unit;
пост-Add сбой для radius>0 не форсировать). Транзитный сбой пакета (нестабильность КОМПАС после
большого прогона) не воспроизводится — повторный прогон 5/5 зелёный.
## Правки по ревью Codex
- **#5** `((IDimensionText)dim).AutoNominalValue = true` выставляем явно (консистентно с линейным).
- **#7** извлечь `RequireSymbols2DContainer(viewNumber)` (= `(ISymbols2DContainer)FindView(...)`) —
переиспользуют диаметральный путь и счётчики (радиальный — следующий, тот же паттерн).
- **#4** конверсия °→рад вынесена в чистый `DimensionAngles.ToRadians(deg)` + unit-тест (значение от
угла не зависит → математику проверяем отдельно).
- **#2** `DimensionType` НЕ задаём — принимаем COM-дефолт (стандартный стиль; полярность bool не
подтверждена, спайк с дефолтом дал `Valid` + верное значение). Зафиксировано комментарием.
- **#1** размер «свободный» (по Xc/Yc/Radius, без `BaseObject`) — как у линейного; LLM задаёт верные
параметры окружности. Ассоциативная привязка — будущее. Отражено в описании инструмента.
- **#3** `angle` — любой конечный (направление выноски, КОМПАС нормализует); валидируем лишь
конечность. **#6** откат после `Add()` для диаметрального не форсируется (radius>0 → всегда Valid,
value=2R>0); код отката идентичен протестированному линейному (zero-projection тест).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 4. После линейного размера — **диаметральный** размер окружности на виде
(Ø для отверстий/цилиндров — частый случай конструкторского чертежа). Радиальный (с ветвями) и
угловой — следующие инкременты.
## Спайк: механизм подтверждён (НЕ разучивать)
Та же модель, что у линейного размера (`ISymbols2DContainer` QI от вида). Спайк
(`DrawingDiametralSpikeTests`, зелёный):
```
ISymbols2DContainer symbols = (ISymbols2DContainer)view; // QI от конкретного IView
IDiametralDimension dim = symbols.DiametralDimensions.Add();
dim.Xc = xc; dim.Yc = yc; // центр окружности (ЛОКАЛЬНАЯ СК вида, мм)
dim.Radius = radius; // радиус окружности (мм)
dim.Angle = angleRad; // направление выноски (РАДИАНЫ)
bool ok = dim.Update();
double value = ((IDimensionText)dim).NominalValue; // = ДИАМЕТР (2·Radius)
```
**Проверено:** `Xc=10,Yc=10,Radius=15,Angle=0.785``Update=True`, `Valid=True`,
`NominalValue == 30` (= диаметр = 2·Radius). `Angle` — в **радианах** (конвенция 2D-API КОМПАС;
для размера задаёт лишь направление выноски, на значение не влияет).
**Координаты** — локальная СК вида (как у линейного). Размер «свободный» (по Xc/Yc/Radius), без
ассоциативной привязки к геометрии (`BaseObject` не задаём — будущее).
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_add_diametral_dimension` | `xc,yc` (центр), `radius`, `angle=0` (°), `viewNumber=0` | Поставить диаметральный размер (Ø) окружности с центром (xc,yc) и радиусом `radius` (ЛОКАЛЬНАЯ СК вида, мм) на виде. `angle` — направление выноски в ГРАДУСАХ (внутри → радианы). `viewNumber`: 0 = первый/главный вид. Значение (диаметр = 2·radius) измеряется автоматически. Возвращает диаметр и номер вида. |
`angle` экспонируется в **градусах** (LLM-дружелюбно), внутри умножается на π/180.
## Архитектура
В существующем `DrawingService` (рядом с `AddLinearDimensionAsync`); переиспользуем `FindView`,
`DrawingDimensionResult`, паттерн отката.
- `DrawingService.AddDiametralDimensionAsync(viewNumber, xc, yc, radius, angleDeg, ct)`.
- Инструмент `drawing_add_diametral_dimension` в `DrawingTools`.
- Helper `GetViewDiametralDimensionCountAsync` (для теста — попадание в вид; размеры в
`ISymbols2DContainer`, не в `ObjectCount` — как выяснено для линейных).
## Валидация (чистые static, unit-тест)
- `DrawingValidation.RequireFiniteCoords(xc, yc, angleDeg)` (уже есть).
- `DrawingValidation.RequirePositiveRadius(double radius)` (новый) — конечный и `> 0`.
## Реализация
`AddDiametralDimensionCore`: валидация (coords + radius>0) → `FindView``(ISymbols2DContainer)view`
`DiametralDimensions.Add()` (null-check) → `Xc/Yc/Radius`, `Angle = angleDeg·π/180``Update()`
(FALSE → откат `dim.Delete` + ошибка) → `Valid` (false → откат) → `value = ((IDimensionText)dim).
NominalValue` (диаметр); если `≤ 0` → откат + ошибка → вернуть `DrawingDimensionResult { Value, ViewNumber }`.
RCW точечно не освобождаем (v2-2).
## Тестирование
### Unit (`DrawingValidationTests`)
- `RequirePositiveRadius` — бросает на 0/отрицательном/NaN/Infinity; пропускает положительный.
### Integration (`DrawingDiametralTests`)
1. **Значение = диаметр**: Radius=15 → `Value ≈ 30`.
2. **Попадание в вид**: `*DiametralDimensions.Count` целевого вида +1 (адресация по `viewNumber` из
`ViewNumbers`).
3. **Радиус ≤ 0**`ArgumentOutOfRangeException` (откат не нужен — до `Add`).
4. **Нет видов** → понятная ошибка.
(Сценарий 1 = промотированный спайк; спайк-файл удаляется.)
## Дальнейшее (вне спека)
- Радиальный размер (`RadialDimensions`, ветви), угловой; ассоциативная привязка к окружности вида
(`BaseObject`); рамка/формат листа.
@@ -1,138 +0,0 @@
# Дизайн: линейный размер на виде чертежа (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: `part``IModelContainer`.)
- **Координаты X1..Y3 — ЛОКАЛЬНАЯ СК вида** (= координаты модели на плоскости вида, при масштабе
1:1 совпадают с мм модели). НЕ координаты листа.
- **`AutoNominalValue=true` по умолчанию** → `NominalValue` = расстояние между точками
(горизонтальный: `|X2-X1|`, вертикальный: `|Y2-Y1|`, параллельный: реальная длина). Значение
читается `((IDimensionText)dim).NominalValue` (QI).
- `Orientation` (`ksLineDimensionOrientationEnum`): `ksLinDParallel=0`, `ksLinDHorizontal=1`,
`ksLinDVertical=2``Kompas6Constants`).
- `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)`, `ToKompas``ksLineDimensionOrientationEnum`).
- `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)view`
`LineDimensions.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`);
допуски/обозначения; рамка/формат листа.
@@ -1,182 +0,0 @@
# Дизайн: радиальный (R) и угловой размеры на виде чертежа через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (5 радиальных + 8 угловых интеграционных + 9 unit; всего 240 тестов зелёных); ревью Codex спека учтено
## Уточнение по реализации (важно)
Спайк изначально решил, что измеряемый сектор выбирает положение дуги `(x3,y3)`. **При реализации
выяснилось иначе:** какой угол измерять, выбирает **`angleType`** (`ksADMinAngle`/`MaxAngle`/`MoreAngle`),
а `(x3,y3)` лишь позиционирует размерную дугу и задаёт её радиус. (270° в спайке возникал потому, что
`X3/Y3` не были заданы = совпадали с вершиной → вырождение.)
Три типа дают **три разных угла** (проверено на лучах 0° и 45°):
- `Min` (`ksADMinAngle`) → **45°** (острый);
- `Max` (`ksADMaxAngle`) → **135°** (тупой супплемент, 180−45);
- `More` (`ksADMoreAngle`) → **315°** (рефлексный, 360−45).
Тесты (#8 More→270 на 90°, Max→135 на 45°) и описания инструмента/сервиса исправлены под это.
## Правки по ревью Codex (реализация)
- **#1** описание параметра `x3,y3` в инструменте переформулировано (точка позиционирует дугу; угол
выбирает `angleType`, не она).
- **#2** сообщение пост-проверки углового больше не винит `(x3,y3)` — указывает на коллинеарность сторон.
- **#3** добавлен интеграционный тест `angleType=Max` (лучи 0°/45° → 135°) — покрыл маппинг `ksADMaxAngle`,
отличный от `Min`/`More`.
- **#4** (откат радиального не тестируется) — **отклонено с обоснованием:** как у диаметрального
(`radius>0` → всегда `Valid`, `value=radius>0`; детерминированного пост-Add сбоя нет), код отката
структурно идентичен протестированному угловому (коллинеарный путь, счётчик не меняется).
## Правки по ревью Codex (спек)
- **#1** угловой: добавлен `RequireDistinctPoints(x1,y1,x2,y2)` (совпавшие точки сторон → одинаковые
лучи → вырождение ещё до `Add`).
- **#3** добавлен тест отката после `Add`: коллинеарные сонаправленные лучи (вершина(0,0),(10,0),(20,0))
→ угол ≈0 → пост-проверка `value<=0` → откат `Delete` + ошибка; счётчик угловых не изменился.
- **#2** поведение `x3,y3` (выбор сектора) задокументировано в описании инструмента; добавлен
интеграционный тест выбора сектора (та же геометрия, `x3,y3` в дополнительном секторе → ~270°).
Большой/тупой угол НЕ отвергаем — это легитимный результат (рефлексный угол через `more`/положение дуги).
- **#4** добавлены тесты на несуществующий `viewNumber>0` (ветка `FindView` «вид не найден») для обоих.
- **#5** `DrawingDimensionResult.Value` — XML-док станет единице-нейтральным («значение размера»);
ответ `drawing_add_angular_dimension` форматируется в градусах (°), не «мм».
- **#6** радиальный использует `DimensionAngles.ToRadians(angleDeg)` (как диаметральный), не ручное `·π/180`.
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 5. После линейного и диаметрального размеров — **радиальный (R)** и
**угловой** размеры на виде. Завершают базовое семейство размеров чертежа. Оба переиспользуют
проверенный путь `ISymbols2DContainer` (QI от `IView`) и паттерн «Add → set → Update → проверка
`Valid` → чтение `NominalValue` → откат `Delete` при ошибке».
## Спайк: поведение подтверждено вживую (НЕ разучивать)
Спайк (`_SpikeDimensions`, прогнан на реальном КОМПАС, затем удалён) на коробке 40×30×20 с
тремя стандартными видами. Контейнер размеров — `(ISymbols2DContainer)view`.
### Радиальный
```
IRadialDimension rd = symbols.RadialDimensions.Add();
rd.Xc = 10; rd.Yc = 10; rd.Radius = 15; rd.Angle = π/4; // Angle — РАДИАНЫ (направление выноски)
rd.DimensionType = true; // BOOL: на значение НЕ влияет (стиль)
((IDimensionText)rd).AutoNominalValue = true;
rd.Update(); // True, rd.Valid == True
double value = ((IDimensionText)rd).NominalValue; // == 15 — РАДИУС (не диаметр!)
```
**Ключевой факт (расходится со справкой SDK):** у радиального `NominalValue` возвращает **радиус**
(`Radius=15 → NominalValue=15`), а НЕ диаметр. Проверено для `DimensionType` и `true`, и `false`
оба `Valid`, значение одинаковое (BOOL влияет лишь на стиль «от центра / нет», не на величину).
Координаты — локальная СК вида, мм. Размер «свободный» (по `Xc/Yc/Radius`, без `BaseObject`).
### Угловой
```
IAngleDimension ad = symbols.AngleDimensions.Add(DrawingObjectTypeEnum.ksDrADimension); // =10
ad.Xc = 0; ad.Yc = 0; // вершина угла
ad.X1 = 10; ad.Y1 = 0; // точка на стороне 1 → луч (Xc,Yc)→(X1,Y1)
ad.X2 = 0; ad.Y2 = 10; // точка на стороне 2 → луч (Xc,Yc)→(X2,Y2)
ad.X3 = 5; ad.Y3 = 5; // положение размерной дуги — ЗАДАЁТ ИЗМЕРЯЕМЫЙ СЕКТОР + радиус дуги
ad.DimensionType = ksADMinAngle; // 0=min / 1=max / 2=more180
((IDimensionText)ad).AutoNominalValue = true;
ad.Update(); // True, ad.Valid == True
double value = ((IDimensionText)ad).NominalValue; // == 90 — ГРАДУСЫ
```
**Проверено:**
- вершина(0,0), точки (10,0)&(0,10), `X3/Y3`=(5,5) → **90°** ✓;
- вершина(0,0), точки (10,0)&(10,10), `X3/Y3`=(8,4) → **45°** ✓.
- **`X3/Y3` критичен:** без него тот же угол измерился как **270°** (дополнительный сектор). Точка
положения дуги задаёт, какой из двух секторов меряется; заодно радиус дуги пересчитывается в
`dist(вершина, X3/Y3)` (заданный `Radius` перетирается → его не экспонируем).
- Подход через `Angle1/Angle2` (углы наклона сторон) — **вырожденный** (точки схлопываются к
вершине, `X1≈0.001`), не используем. Геометрию ведут только координаты точек.
- `NominalValue` — в **градусах**.
`Add(DrawingObjectTypeEnum)` требует код типа: `ksDrADimension = 10` (обычный угловой),
`ksDrABreakDimension = 39` (с обрывом — вне объёма).
## MCP-инструменты
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_add_radial_dimension` | `xc,yc` (центр окружности), `radius`, `angle=0` (°), `viewNumber=0` | Радиальный размер (R) окружности/дуги с центром (xc,yc) и радиусом `radius` (ЛСК вида, мм). `angle` — направление выноски в ГРАДУСАХ (внутри → радианы). `viewNumber`: 0 = первый/главный. Значение (= радиус) измеряется автоматически. Возвращает радиус и номер вида. |
| `drawing_add_angular_dimension` | `xc,yc` (вершина), `x1,y1` (точка на стороне 1), `x2,y2` (точка на стороне 2), `x3,y3` (положение размерной дуги — задаёт сектор), `angleType="min"`, `viewNumber=0` | Угловой размер между лучами (xc,yc)→(x1,y1) и (xc,yc)→(x2,y2) (ЛСК вида, мм). `x3,y3` должны лежать в измеряемом секторе. `angleType`: min/max/more. Значение (градусы) измеряется автоматически. Возвращает угол (°) и номер вида. |
Углы выноски (`angle`) экспонируются в **градусах** (LLM-дружелюбно), внутри `·π/180`.
## Архитектура
В существующем `DrawingService` (рядом с `AddDiametralDimensionAsync`); переиспользуем
`RequireSymbols2DContainer(viewNumber)`, `DrawingDimensionResult`, паттерн отката.
- `DrawingService.AddRadialDimensionAsync(viewNumber, xc, yc, radius, angleDeg, ct)`.
- `DrawingService.AddAngularDimensionAsync(viewNumber, xc, yc, x1, y1, x2, y2, x3, y3, angleType, ct)`.
- Инструменты `drawing_add_radial_dimension`, `drawing_add_angular_dimension` в `DrawingTools`.
- Helpers `GetViewRadialDimensionCountAsync`, `GetViewAngularDimensionCountAsync` (для тестов —
попадание в вид; размеры в `ISymbols2DContainer.RadialDimensions/AngleDimensions.Count`, не в
`IView.ObjectCount`).
- Новый файл `src/Kompas.Mcp.Core/Drawings/AngleDimensionType.cs`: `enum AngleDimensionType {Min,Max,More}`
+ static `AngleDimensionTypes.Parse(string)` / `ToKompas(...)` (→ `ksADMinAngle/ksADMaxAngle/ksADMoreAngle`),
по образцу `DimensionOrientations`.
`DrawingDimensionResult` (Value + ViewNumber) переиспользуется: радиальный `Value`=радиус, угловой
`Value`=градусы.
## Валидация (чистые static, unit-тест)
- Радиальный: `RequireFiniteCoords(xc, yc, angleDeg)` + `RequirePositiveRadius(radius)` — уже есть.
- Угловой: `RequireFiniteCoords(xc,yc,x1,y1,x2,y2,x3,y3)` (8 координат) + `RequireDistinctPoints`
четырежды: вершина≠(x1,y1), вершина≠(x2,y2), вершина≠(x3,y3) (иначе дуга нулевого радиуса),
(x1,y1)≠(x2,y2) (#1: совпавшие точки сторон → одинаковые лучи). Все уже есть; новых валидаторов не требуется.
## Реализация
**`AddRadialDimensionCore`** (зеркало диаметрального, но значение = радиус): валидация → `RequireSymbols2DContainer`
`RadialDimensions.Add()` (null-check) → `Xc/Yc/Radius`, `Angle=DimensionAngles.ToRadians(angleDeg)` (#6), `DimensionType=true`
(стиль R от центра, явно), `AutoNominalValue=true``Update()` (FALSE → откат `Delete`+ошибка) →
`Valid` (false → откат) → `value = NominalValue` (радиус); `value<=0 || !IsFinite` → откат+ошибка →
`DrawingDimensionResult{Value, ViewNumber}`.
**`AddAngularDimensionCore`**: валидация → `RequireSymbols2DContainer``AngleDimensions.Add(ksDrADimension)`
(null-check) → `Xc/Yc/X1/Y1/X2/Y2/X3/Y3`, `DimensionType=AngleDimensionTypes.ToKompas(angleType)`,
`AutoNominalValue=true``Update()` (FALSE → откат) → `Valid` (false → откат) → `value=NominalValue`
(°); `value<=0 || !IsFinite` → откат+ошибка с подсказкой (вырожденный угол: стороны коллинеарны или
`x3,y3` вне сектора) → `DrawingDimensionResult{Value, ViewNumber}`. Угол в (0,360); защиту по верхней
границе не форсируем (КОМПАС нормализует, `more` может дать >180).
RCW точечно не освобождаем (консистентно с линейным/диаметральным; долг v2-2).
## Тестирование
### Unit (`DrawingDimensionTypeTests` — новый, либо в `DrawingValidationTests`)
- `AngleDimensionTypes.Parse`: `min/max/more` (+ рус. синонимы) → enum; неизвестное → `ArgumentException`.
- `AngleDimensionTypes.ToKompas`: маппинг трёх значений → `ksADMinAngle/ksADMaxAngle/ksADMoreAngle`.
- (`ToRadians`, `RequirePositiveRadius`, координаты — уже покрыты.)
### Integration (`DrawingRadialTests`, `DrawingAngularTests`; наследуют `IntegrationTestBase`)
Радиальный:
1. **Значение = радиус**: `radius=12 → Value ≈ 12` (промотированный спайк).
2. **Попадание в вид**: `RadialDimensions.Count` целевого вида +1 (адресация по `viewNumber`).
3. **Радиус ≤ 0**`ArgumentOutOfRangeException` (до `Add`).
4. **Нет видов** → понятная ошибка.
Угловой:
5. **Измеряет 90°**: вершина(0,0), (10,0)&(0,10), x3/y3=(5,5) → `Value ≈ 90`.
6. **Измеряет 45°**: вершина(0,0), (10,0)&(10,10), x3/y3=(8,4) → `Value ≈ 45`.
7. **Попадание в вид**: `AngleDimensions.Count` целевого вида +1.
8. **Выбор угла типом** (#2): `angleType=More` на геометрии 90° → `Value ≈ 270`; `angleType=Max`
на лучах 0°/45° → `Value ≈ 135` (тупой супплемент). Какой угол измерять, выбирает `angleType`, не `x3,y3`.
9. **Вырожденный угол до Add** (вершина совпадает с точкой стороны) → `ArgumentException`.
10. **Откат после Add** (#3): коллинеарные сонаправленные лучи (0,0),(10,0),(20,0) → угол ≈0 →
`InvalidOperationException`; `AngleDimensions.Count` целевого вида НЕ изменился (откат `Delete`).
11. **Несуществующий `viewNumber>0`** (#4) → понятная ошибка (ветка `FindView`).
12. **Нет видов** → понятная ошибка.
(Радиальный — аналогично добавлен тест на несуществующий `viewNumber>0`, #4.)
## Дальнейшее (вне спека)
- Ассоциативная привязка размеров к геометрии вида (`BaseObject`/`BaseObject1/2`); рамка/формат листа;
текстовые обозначения (шероховатость `Roughs`, допуски формы `Tolerances`, выноски `Leaders`,
тех. требования); радиальный с изломом (`BreakRadialDimensions`), угловой с обрывом (`ksDrABreakDimension`).
@@ -1,133 +0,0 @@
# Дизайн: формат и ориентация листа чертежа (drawing_set_sheet_format) через API7
**Дата:** 2026-05-27
**Статус:** дизайн согласован, спайк проведён, ревью pi/glm-5.1 + pi/kimi-k2.6 учтено — к реализации
## Правки по ревью реализации (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- **K1 (kimi, Major)** — опасение, что для `User` read-back ориентации устаревший (VerticalOrientation
не задаётся). **Снято спайком:** КОМПАС САМ выводит `VerticalOrientation` из соотношения W/H
(500×300→альбомная, 300×500→книжная), игнорирует заданный флаг и НЕ свопает W/H → read-back верный,
код корректен. Документировано комментарием; добавлены тесты на `Landscape` для User (обе ориентации).
- **K2** — `ValidateFormatDimensions` теперь называет точный нарушивший параметр (`width`/`height`).
- **C1/G1/K3** — тесты User проверяют `Landscape`. **C2/G3** — тест `sheetNumber=0``ArgumentOutOfRangeException`.
**C3** — A1 в `Parse`-тесте. **G2** — тест A4 landscape (своп 297×210).
- Отклонено (нит): **G4** алиасы в описании (lenient parse — намеренно), **G5** сообщение при NaN
(для стандартного «размеры только для user» приемлемо), **K5** тип исключения для стандартного формата (ArgumentException
для «неверная комбинация» vs AOORE для «вне диапазона» — намеренное различие, на нём держатся тесты),
**G6** «нет unit для ValidateFormatDimensions» — ложно, покрыто в `DrawingValidationTests`.
## Правки по ревью спека (pi/glm-5.1 + pi/kimi-k2.6)
- **A** для `user` флаг `landscape` **игнорируется** — ориентация задаётся соотношением `width`/`height`
(КОМПАС сам выставит `VerticalOrientation`). `VerticalOrientation=!landscape` ставим ТОЛЬКО для
стандартных форматов (избегаем неоднозначного свопа W/H). Задокументировано в описании инструмента.
- **B/E** `width`/`height` для стандартного формата запрещены: валидатор `ValidateFormatDimensions`
(для `User` — оба конечны и `>0`; для стандартного — оба `==0`, иначе `ArgumentException` «размеры
только для user»).
- **C** read-back формата возвращает дружелюбное имя через новый `PaperFormats.FromKompas(ksEnum)`
`SheetFormatResult.Format` = `"A3"`/`"User"` (симметрия вход/выход), а не COM-имя `"ksFormatA3"`.
- **D** `RequireLayoutSheet(sheetNumber)`: `sheetNumber>0` (`ArgumentOutOfRangeException`) + null-check
после `ItemByNumber[sheetNumber]` (`InvalidOperationException` «нет листа N»); + интеграционный тест.
- **F** read-back `fmt2 = sheet.Format` с null-check.
- **G** `SheetFormatResult``public sealed record` с `public required … { get; init; }` (как
`DrawingDimensionResult`/`DrawingViewsResult`).
- **H** интеграционные сравнения размеров — через `Assert.InRange` (допуск на float).
- **I** члены `PaperFormat`с XML-`<summary>` (как `RoughSignType`).
- **J** `FormatMultiplicity` оставляем на COM-дефолте (1) — отмечено в реализации.
- **K** (верхняя граница `width`/`height`) — отклонено, в OPEN_QUESTIONS как будущее упрочнение
(консистентно с `RequirePositiveRadius` — верхней границы нет).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 8. Задать **формат** (A0–A5 или пользовательский) и **ориентацию**
(книжная/альбомная) листа активного чертежа. Фундамент «правильного» по ГОСТ чертежа (сейчас лист
всегда дефолтный A4 книжный).
## Спайк: механизм подтверждён вживую (НЕ разучивать)
Спайк (`_SpikeSheetFormat`, прогнан на реальном КОМПАС, затем удалён): новый чертёж →
`doc.LayoutSheets.ItemByNumber[1]` (`ILayoutSheet`) → `sheet.Format` (`ISheetFormat`).
```
ISheetFormat fmt = sheet.Format;
fmt.Format = ksDocumentFormatEnum.ksFormatA3; // A0=0..A5=5, User=6
fmt.VerticalOrientation = false; // true=книжная (portrait), false=альбомная (landscape)
sheet.Update(); // True
// для стандартного формата FormatWidth/Height пересчитываются АВТО (A3 landscape → 420×297)
```
**Проверено:**
- Дефолт нового чертежа: `ksFormatA4`, `VerticalOrientation=true`, 210×297, `FormatMultiplicity=1`.
- A3 landscape: `Update=True`, read-back `Format=ksFormatA3`, `Vertical=False`, **W=420, H=297 (авто)**
для стандартных форматов размеры не задаём, их даёт enum+ориентация.
- A4 portrait: 210×297.
- Пользовательский: `Format=ksFormatUser` + `FormatWidth=500` + `FormatHeight=300` + `Update` → применён.
`ksDocumentFormatEnum`: `ksFormatA0=0`, `A1=1`, `A2=2`, `A3=3`, `A4=4`, `A5=5`, `ksFormatUser=6`.
`ISheetFormat`: `Format` (enum), `VerticalOrientation` (bool), `FormatWidth`/`FormatHeight` (double, мм),
`FormatMultiplicity` (int).
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_set_sheet_format` | `format="A4"` (A0\|A1\|A2\|A3\|A4\|A5\|user), `landscape=false` (false=книжная), `width=0`,`height=0` (ТОЛЬКО для `user`, мм, >0), `sheetNumber=1` | Задать формат и ориентацию листа `sheetNumber` активного чертежа. Стандартный формат — размеры авто (по enum+ориентации), `width`/`height` для него запрещены (должны быть 0). `user` — задать `width`/`height` (>0); для `user` флаг `landscape` ИГНОРИРУЕТСЯ (ориентация — из соотношения сторон). Возвращает итоговый формат («A3»/«User»), ширину, высоту, ориентацию (read-back). |
## Архитектура
В `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`).
- `SetSheetFormatAsync(PaperFormat format, bool landscape, double width, double height, int sheetNumber, ct)`
`SheetFormatResult`.
- Helper `RequireLayoutSheet(sheetNumber)``ILayoutSheet`: `sheetNumber>0`
(`ArgumentOutOfRangeException`) → нет активного чертежа / `LayoutSheets` null / `ItemByNumber[sheetNumber]`
null («нет листа N») — раздельная диагностика, рядом с `RequireActiveStamp`.
- Новый файл `src/Kompas.Mcp.Core/Drawings/PaperFormat.cs`: `enum PaperFormat {A0,A1,A2,A3,A4,A5,User}`
(каждый член с XML-`<summary>`) + `PaperFormats.Parse(string)` / `ToKompas(PaperFormat)→ksDocumentFormatEnum`
/ `FromKompas(ksDocumentFormatEnum)→PaperFormat` (для read-back дружелюбного имени), по образцу `RoughSignTypes`.
- Новый файл `src/Kompas.Mcp.Core/Drawings/SheetFormatResult.cs`:
`public sealed record SheetFormatResult { public required string Format; public required double Width;
public required double Height; public required bool Landscape; }` (стиль `DrawingDimensionResult`).
## Реализация
`SetSheetFormatCore`: `DrawingValidation.ValidateFormatDimensions(format, width, height)`
`PaperFormats.ToKompas(format)``RequireLayoutSheet(sheetNumber)``fmt = sheet.Format` (null-check) →
`fmt.Format = ksEnum`; if `format==User`: `fmt.FormatWidth=width`, `fmt.FormatHeight=height` (ориентацию
НЕ трогаем — задаётся W/H); else: `fmt.VerticalOrientation = !landscape` (размеры авто) → `sheet.Update()`
(FALSE → ошибка; объект уровня листа, перезапись идемпотентна — отката `Delete` нет) → read-back
`fmt2 = sheet.Format` (null-check) → `SheetFormatResult { Format = PaperFormats.FromKompas(fmt2.Format)
.ToString(), Width = fmt2.FormatWidth, Height = fmt2.FormatHeight, Landscape = !fmt2.VerticalOrientation }`.
`FormatMultiplicity` оставляем на COM-дефолте (1) — не читаем и не задаём. RCW точечно не освобождаем
(консистентно; долг v2-2).
## Валидация (чистые static, unit)
- `PaperFormats.Parse``A0..A5`/`user` (+ регистр/пробелы); неизвестное → `ArgumentException`; null → `ArgumentNullException`.
- Новый `DrawingValidation.ValidateFormatDimensions(PaperFormat format, double width, double height)`:
для `User``width` и `height` оба конечны и `> 0` (иначе `ArgumentOutOfRangeException`); для стандартного —
`width==0 && height==0` (иначе `ArgumentException` «размеры задаются только для format=user»).
- `PaperFormats.ToKompas`/`FromKompas` (↔ `ksDocumentFormatEnum`) НЕ покрываем unit — граница проекта
(тест не ссылается на `Kompas6Constants`), как у прочих enum-маппингов; покрываются интеграцией.
## Тестирование
### Unit (`PaperFormatsTests`, `DrawingValidationTests`)
- `PaperFormats.Parse`: `A4/a3/ A2 /a0/a5/user` → enum; неизвестное → `ArgumentException`; null → `ArgumentNullException`.
- `ValidateFormatDimensions`:
- `User`: бросает на (0,300)/(500,0)/(1,300)/(NaN,300)/(500,∞); пропускает (500,300).
- стандартный (`A4`): бросает на (500,0)/(0,300)/(500,300); пропускает (0,0).
### Integration (`DrawingSheetFormatTests`; сравнения размеров — `Assert.InRange`)
1. **A3 landscape**: `format=A3, landscape=true``Format=="A3"`, `Width≈420`, `Height≈297`, `Landscape==true`.
2. **A4 portrait** (`landscape=false`): → `Format=="A4"`, `Width≈210`, `Height≈297`, `Landscape==false`.
3. **User 500×300**: `format=user, width=500, height=300``Format=="User"`, `Width≈500`, `Height≈300`.
4. **User без размеров** (`width=0`) → `ArgumentOutOfRangeException` (до COM).
5. **Размеры для стандартного** (`format=A3, width=400`) → `ArgumentException` (до COM).
6. **Несуществующий лист** (`sheetNumber=99`) → `InvalidOperationException`.
7. **Активный документ не чертёж** (деталь) → понятная ошибка.
## Дальнейшее (вне спека)
- Рамка/основная надпись по конкретному ГОСТ-стилю оформления (`LayoutLibraryFileName`/`LayoutStyleNumber`),
несколько листов (`LayoutSheets.Add`); привязка к дуге, угловой/шероховатость; выноски/базы/допуски формы.
@@ -1,128 +0,0 @@
# Дизайн: стандартные виды чертежа (drawing_create_standard_views) через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (3 интеграционных + 18 unit, всего 177); ревью Codex спека (10) и реализации (7) учтено
## Правки по ревью реализации Codex (7 замечаний)
- **#1** непустоту проверяем у ВНОВЬ созданных видов (по разнице `IView.Number` до/после), а не по
всей коллекции — на непустом чертеже прежняя геометрия не маскирует пустые новые виды.
- **#3** откат: при `created != 3` или пустых видах удаляем созданные виды (`IView.Delete`) → чертёж
не остаётся в полу-состоянии.
- **#2** ленивость `ObjectCount` опровергнута эмпирически (happy-тест зелёный = геометрия есть сразу,
rebuild не нужен). **#7** добавлен unit-тест нормализации кириллического пути.
- **#4** повторный вызов намеренно добавляет ещё набор (дубликаты в разных `x,y` легитимны) —
отражено в описании инструмента. **#5** обход `IViews` уточнён (по `Number`); точечный release RCW
отклонён (v2-2). **#6** доп. негативы (.a3d, пустой валидный .m3d) — маргинальны, отложены.
## Решения по ревью Codex
- **#1** файл модели: `File.Exists` И `FileInfo.Length > 0` (несохранённый/битый файл).
- **#3/#5/#6** непустота видов — гарантия В СЕРВИСЕ: после создания суммируем `IView.ObjectCount`
по коллекции; если 0 → ошибка «виды пусты (модель не спроецировалась)». Это и проверяет, что
rebuild не нужен (геометрия уже есть). Координатные проверки (`GetProjectionPoint`) — избыточны.
- **#8** строгий контракт: `created == 3` (иначе ошибка с `before/after/ok`), а не `> 0`.
- **#9** позиция главного вида — опциональные `x`,`y` (дефолт 100/150), чтобы повторный вызов не
клал виды стопкой; `dx`/`dy`=20 фиксированы (YAGNI).
- **#10** возврат — record `DrawingViewsResult { Created, Total }` (не голый int) для отчёта и
будущей адресации видов.
- **#2** негативный unit-тест на чужое расширение (`.step`) — в плане.
- **#4** точечный release RCW отклонён (конвенция v2-2, как `AssemblyService`). **#7** неверный
`ProjectionName` не тестируем — `"#Спереди"` захардкожен, не управляется пользователем.
## Цель
Веха 2 — **2D-ЧЕРТЁЖ** (новый класс функционала после деталей и сборок). Инкремент 1: создать на
активном чертеже **стандартные ассоциативные виды** (спереди/сверху/слева) по сохранённой 3D-модели
`.m3d`/`.a3d`. Это фундамент чертежа — без видов лист пуст. Чертёж проверяется не объёмом, а
**числом созданных видов**.
## Спайк: механизм подтверждён вживую (НЕ разучивать)
Спайк (`DrawingSpikeTests`, зелёный) подтвердил путь API7:
```
IKompasDocument2D doc2d = (IKompasDocument2D)app.ActiveDocument; // активный чертёж
IViews views = doc2d.ViewsAndLayersManager.Views; // COM-свойства (не QI)
object[] projTypes = { 1, 3, 5 }; // ksPtFront=1, ksPtUp=3, ksPtLeft=5 (SAFEARRAY VT_I4)
bool ok = views.AddStandartViews(path, "#Спереди", projTypes, X:100, Y:150, Scale:1.0, DX:20, DY:20);
int count = views.Count;
```
**Проверенные факты:**
- `object[] { 1, 3, 5 }` **маршалится в SAFEARRAY VT_I4 без проблем** (риск из ревью SDK снят).
- Новый чертёж стартует с `Views.Count == 1` (системный вид #0). После `AddStandartViews` с тремя
кодами проекций `Count == 4`**создано ровно 3 вида** (спереди/сверху/слева). `ok == True`.
- `ProjectionName = "#Спереди"` — ориентация главного вида (стандартное имя проекции 3D-модели).
- `IKompasDocument2D` напрямую (без QI к `IKompasDocument2D1`) даёт `ViewsAndLayersManager.Views`
(подтверждено рефлексией interop — отличие от ревью SDK).
- Явный rebuild/save после `AddStandartViews` НЕ нужен — виды уже в документе (`Count` сразу 4).
**Параметры `AddStandartViews(FileName, ProjectionName, ProjectionsTypes, X, Y, Scale, DX, DY)`:**
- `FileName` — путь к `.m3d`/`.a3d`.
- `ProjectionName` — ориентация главного вида (`"#Спереди"`).
- `ProjectionsTypes``SAFEARRAY VT_I4` кодов `ksRelativeProjectionTypeEnum`: `ksPtFront=1`,
`ksPtRear=2`, `ksPtUp=3`, `ksPtDown=4`, `ksPtLeft=5`, `ksPtRight=6`, `ksPtIsoXYZ=7`.
- `X, Y` — точка привязки главного вида на листе, мм.
- `Scale` — масштаб (1.0 = 1:1).
- `DX, DY` — зазоры между видами, мм. Возврат: `bool` (успех).
## Архитектура
Новый класс (как сборки — отдельный от `PartModeler`). Сервис `DrawingService` (API7), инструменты
`DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`.
- `src/Kompas.Mcp.Core/Drawings/DrawingService.cs` — сервис.
- `src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs` — чистые static-валидаторы (unit).
- `src/Kompas.Mcp.Host/Tools/DrawingTools.cs` — MCP-инструмент.
- DI: `AddSingleton<DrawingService>()` в `Program.cs`.
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_create_standard_views` | `partFilePath: string`, `scale=1.0` | На активном чертеже создать стандартные ассоциативные виды (спереди/сверху/слева) сохранённой детали/сборки. Возвращает число созданных видов. |
Контракт: активный документ — чертёж (`ksDocumentDrawing`); файл модели существует. Набор видов
фиксирован (спереди+сверху+слева, 3 вида); позиция/зазоры — дефолты (X=100, Y=150, DX=20, DY=20 мм).
Выбор набора видов / позиции / изометрии — будущие инкременты (YAGNI).
## Валидация (чистые static, unit-тест)
`DrawingValidation`:
- `NormalizeModelPath(string? path)``Trim` → непустота → расширение `.m3d`/`.a3d``Path.GetFullPath`.
- `RequireScale(double scale)` — конечный и `> 0`.
`File.Exists` — в сервисе (`FileNotFoundException` с абсолютным путём).
## Проверка активного документа
`RequireActiveDrawing()` — раздельные ошибки: нет документа / тип != `ksDocumentDrawing` / не
приводится к `IKompasDocument2D`.
## Реализация
`CreateStandardViewsAsync(partFilePath, scale, ct)` — на STA-потоке: валидация (`NormalizeModelPath`,
`RequireScale`) → `File.Exists``RequireActiveDrawing``views.Count` (before) →
`AddStandartViews(path, "#Спереди", {1,3,5}, 100, 150, scale, 20, 20)` (результат проверяем: FALSE →
ошибка) → `created = views.Count - before` (ожидаем 3; если 0 — ошибка «виды не созданы»). Возврат —
число созданных видов. Транзитные RCW не освобождаем точечно (долг v2-2).
## Тестирование
### Unit (`DrawingValidationTests`)
- `NormalizeModelPath` — бросает на пустом/чужом расширении (`.step`); принимает `.m3d`/`.a3d`
(регистр, пробелы), возвращает абсолютный путь.
- `RequireScale` — бросает на 0/отрицательном/NaN/Infinity; принимает положительный.
### Integration (`DrawingTests`)
Паттерн: построить деталь 40×30×20 → `SaveAsAsync(.m3d)` → проверить файл → `CloseAsync` → создать
чертёж → `CreateStandardViewsAsync` → проверить → `finally CloseAsync` (база `IntegrationTestBase`).
1. **Создание видов**: `AddStandartViews` → возврат `== 3`; `Views.Count` вырос на 3 (с 1 до 4).
2. **Не-чертёж**: активный документ — деталь → `CreateStandardViewsAsync` бросает (понятная ошибка).
(Сценарий 1 = промотированный спайк; спайк-файл удаляется.)
## Дальнейшее (вне спека)
- Выбор набора видов (+изометрия, +сечения), позиция/масштаб листа, размеры/обозначения на видах,
рамка/основная надпись.
@@ -1,167 +0,0 @@
# Дизайн: текстовые обозначения чертежа — шероховатость, текст, тех. требования (API7)
**Дата:** 2026-05-27
**Статус:** дизайн согласован (все три в одном инкременте), спайк проведён, ревью Codex спека учтено — к реализации
## Правки по ревью Codex (спек)
- **#4** параметр `height` у `drawing_add_text` **убран:** `IDrawingText.Height` — высота блока
форматирования, НЕ размер шрифта (шрифт задаётся на уровне `ITextItem`, вне объёма). Текст ставится
стилем по умолчанию. (Снимает и #10 — валидация height не нужна.)
- **#3/#11** сервис возвращает значение, **прочитанное обратно из COM** после `Update` (rough —
`RoughParamText.Str`; text — `((IText)dt).Str`) — как `NominalValue` у размеров. Тест `Value==вход`
доказывает round-trip. Пост-Add откат `Delete` есть в коде; не форсируется тестом (для валидных
параметров COM всегда `Valid` — как решено у диаметрального; путь идентичен протестированному угловому).
- **#1** `value` шероховатости нормализуется (`null`/whitespace → `""`); возвращается нормализованное
(read-back), контракт `Value` непустой не нарушается.
- **#2/#5/#7** добавлены null/QI-guards: `Roughs`/`(IRoughParams)`/`RoughParamText`;
`(IDrawingContainer)`/`DrawingTexts`/`(IText)`; `(IDrawingDocument)`/`TechnicalDemand`/`Text`
каждый с раздельной диагностикой (как `RequireStampCell`/`RequireSymbols2DContainer`).
- **#6** тесты текста дополнены: многострочный round-trip (`\n`), несуществующий `viewNumber>0`.
- **#8** добавлен helper `ReadTechnicalRequirementsAsync` — тест читает `td.Text.Str` и проверяет
перезапись (как `DrawingStampTests` перечитывают графы).
- **#9** подсчёт строк тех. требований нормализован: `text.Replace("\r\n","\n")`, хвостовые пустые
строки отбрасываются (`"a\n"` → 1 строка).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 6. После завершения базового семейства размеров — **текстовые обозначения**:
знак шероховатости, свободная текстовая надпись, технические требования. Три инструмента покрывают
самые частые «не-размерные» элементы конструкторского чертежа.
## Спайк: все три механизма подтверждены вживую (НЕ разучивать)
Спайк (`_SpikeAnnotations`, прогнан на реальном КОМПАС v24, затем удалён) на коробке 40×30×20 с
тремя стандартными видами.
### Шероховатость — на виде (`ISymbols2DContainer.Roughs`)
```
IRough rough = symbols.Roughs.Add(); // без параметров
rough.BranchX0 = 20; rough.BranchY0 = 25; // положение знака (ЛОКАЛЬНАЯ СК вида, мм)
rough.Angle = 0; // угол наклона оси знака (градусы)
IRoughParams rp = (IRoughParams)rough; // QI (как IDimensionText у размеров)
rp.SignType = ksRoughSignEnum.ksDeleteMaterial; // тип знака
rp.RoughParamText.Str = "Ra 1.6"; // значение (Ra/Rz) — текст
rough.Update(); // True, rough.Valid == True
```
**Проверено:** `Update=True`, `Valid=True`, `RoughParamText.Str` round-trip = `"Ra 1.6"`, `Roughs.Count`
→ 1. `ksRoughSignEnum`: `ksNoProcessingType=0` (без указания обработки), `ksDeleteMaterial=1` (с
удалением слоя материала), `ksWithoutDeleteMaterial=2` (без удаления). Положение — свободные координаты
(`BranchX0/Y0`); `BaseObject` (привязка к контуру) не задаём — будущее.
### Свободный текст — на виде (`IDrawingContainer.DrawingTexts`, НЕ Symbols!)
```
IDrawingContainer dc = (IDrawingContainer)view; // ВНИМАНИЕ: текст в контейнере геометрии,
IDrawingText dt = dc.DrawingTexts.Add(); // а НЕ в ISymbols2DContainer
dt.X = 30; dt.Y = 45; dt.Angle = 0; // точка привязки (ЛОКАЛЬНАЯ СК вида, мм)
((IText)dt).Str = "Образец надписи"; // содержимое (QI к IText), \n — многострочно
dt.Update(); // True, dt.Valid == True
```
**Проверено:** `Update=True`, `Valid=True`, `Str` round-trip, `DrawingTexts.Count`**2** (вид уже
содержал 1 текст — авто-подпись вида; проверять по ДЕЛЬТЕ before+1, не по абсолюту). `ObjectCount`
4→4 — текст **НЕ** входит в `IView.ObjectCount` (как и размеры) → проверять `DrawingTexts.Count`.
### Технические требования — на уровне ДОКУМЕНТА (`IDrawingDocument.TechnicalDemand`)
```
IDrawingDocument dd = (IDrawingDocument)doc; // QI от активного IKompasDocument2D
ITechnicalDemand td = dd.TechnicalDemand; // единый блок на документ
td.Text.Str = "1. Общие допуски по ГОСТ 30893.1.\n2. Острые кромки притупить."; // \n — строки
td.Update(); // True
```
**Проверено:** `IsCreated` False→True (первый `Text.Str`+`Update()` создаёт блок), `Update=True`, текст
с `\n` сохранён построчно. Объект уровня документа (не вида), единственный, над основной надписью.
`Str` замещает содержимое (как у штампа) → инструмент `set` (перезапись).
## MCP-инструменты
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_add_rough` | `x,y` (положение знака), `value=""` (Ra/Rz, напр. "Ra 1.6"), `signType="delete"` (delete\|without\|none), `angle=0` (°), `viewNumber=0` | Поставить знак шероховатости на виде. Положение `x,y` в ЛСК вида (мм). `value` — текст параметра (пусто = знак без значения). Возвращает значение и номер вида. |
| `drawing_add_text` | `x,y` (точка привязки), `text`, `angle=0` (°), `viewNumber=0` | Поставить свободную текстовую надпись на виде стилем по умолчанию. `text` — содержимое (`\n` — многострочно). Возвращает текст (read-back) и номер вида. |
| `drawing_set_technical_requirements` | `text` | Задать технические требования активного чертежа (единый блок над штампом; перезаписывает прежние). `text` — строки через `\n`. Возвращает число строк. |
## Архитектура
В существующем `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`). Шероховатость/текст —
per-view; тех. требования — per-document.
- `AddRoughAsync(viewNumber, x, y, value, signType, angleDeg, ct)``DrawingAnnotationResult`.
Введём `record DrawingAnnotationResult { string Value; int ViewNumber }` (универсальный для rough/text —
`Value`=строка, прочитанная обратно из COM; `ViewNumber`). Тех. требования возвращают число строк (int).
- `AddTextAsync(viewNumber, x, y, text, angleDeg, ct)``DrawingAnnotationResult`.
- `SetTechnicalRequirementsAsync(text, ct)``int` (число строк).
- `ReadTechnicalRequirementsAsync(ct)``string` (для теста — `td.Text.Str`).
- Helpers: `RequireSymbols2DContainer` (есть, для rough), новый `RequireDrawingContainer(viewNumber)`
= guarded `(IDrawingContainer)FindView(...)` (для text), новый `RequireDrawingDocument()` =
guarded `(IDrawingDocument)RequireActiveDrawing()` (для тех. требований). Все QI с раздельной диагностикой.
- Счётчики для тестов: `GetViewRoughCountAsync`, `GetViewTextCountAsync`.
- Новый enum-файл `RoughSignType.cs`: `enum RoughSignType {NoProcessing, DeleteMaterial, WithoutDeleteMaterial}`
+ `RoughSignTypes.Parse(string)`/`ToKompas(...)` (→ `ksRoughSignEnum`), по образцу `AngleDimensionTypes`.
- Новый файл `DrawingAnnotationResult.cs`.
- Инструменты в `DrawingTools.cs`.
## Валидация (чистые static, unit-тест)
- `RequireFiniteCoords` (есть) — координаты rough/text.
- Новый `RequireNonEmptyText(string, paramName)` — для `text` (drawing_add_text, тех. требования).
`value` шероховатости НЕ обязателен (знак без значения допустим; `null`/whitespace → `""`).
- `RoughSignTypes.Parse` — разбор строки (unit-тест; `ToKompas``ksRoughSignEnum` НЕ покрываем unit —
граница проекта, как у `AngleDimensionTypes`/`DimensionOrientations`).
## Реализация
**`AddRoughCore`**: нормализация `value` (`null`/whitespace → `""`) → `RequireFiniteCoords(x,y,angleDeg)`
`RequireSymbols2DContainer``symbols.Roughs` (null-check) → `.Add()` (null-check) → `BranchX0/Y0/Angle`
`(IRoughParams)rough` (null-check QI) → `SignType=ToKompas`, `rp.RoughParamText` (null-check) `.Str = value`
`Update()` (FALSE → откат `Delete`) → `Valid` (false → откат) → читаем `rp.RoughParamText.Str` обратно
`DrawingAnnotationResult{Value=read-back, ViewNumber}`.
**`AddTextCore`**: `RequireNonEmptyText(text)` + `RequireFiniteCoords(x,y,angleDeg)`
`RequireDrawingContainer``dc.DrawingTexts` (null-check) → `.Add()` (null-check) → `X/Y/Angle`
`(IText)dt` (null-check QI) `.Str = text``Update()` (FALSE → откат `Delete`) → `Valid` (false → откат)
→ читаем `((IText)dt).Str` обратно → `DrawingAnnotationResult{Value=read-back, ViewNumber}`.
**`SetTechnicalRequirementsCore`**: `RequireNonEmptyText(text)` → нормализация `text.Replace("\r\n","\n")`
`RequireDrawingDocument()``dd.TechnicalDemand` (null-check) → `td.Text` (null-check) `.Str = text`
`td.Update()` (FALSE → ошибка; объект уровня документа — отката `Delete` НЕ делаем, перезапись идемпотентна)
→ вернуть число непустых-после-trim хвоста строк (`TrimEnd('\n').Split('\n').Length`).
**`RequireDrawingContainer(viewNumber)`** = `FindView``(IDrawingContainer)view` (null → «вид не приводится
к IDrawingContainer»). **`RequireDrawingDocument()`** = `RequireActiveDrawing()``(IDrawingDocument)doc`
(null → «чертёж не приводится к IDrawingDocument»).
RCW точечно не освобождаем (консистентно с остальным `DrawingService`; долг v2-2).
## Тестирование
### Unit (`RoughSignTypesTests`, `DrawingValidationTests`)
- `RoughSignTypes.Parse`: `delete/without/none` (+ рус. синонимы) → enum; неизвестное → `ArgumentException`; null → `ArgumentNullException`.
- `RequireNonEmptyText`: бросает на null/пусто/пробелы; пропускает непустое.
### Integration (`DrawingRoughTests`, `DrawingTextTests`, `DrawingTechReqTests`; наследуют `IntegrationTestBase`)
Шероховатость:
1. **Ставится + round-trip**: `value="Ra 1.6"`, `signType=delete``Value=="Ra 1.6"` (read-back из COM),
попадание в целевой вид (`Roughs.Count` +1, адресация по `viewNumber`).
2. **Знак без значения**: `value=""` → ставится (Valid), `Roughs.Count` +1.
3. **Нет видов** → понятная ошибка. 4. **Несуществующий `viewNumber>0`** → ошибка.
Текст:
5. **Ставится + round-trip**: `text``Value==text` (read-back), `DrawingTexts.Count` +1 (по ДЕЛЬТЕ —
вид уже мог содержать подпись), адресация по целевому `viewNumber`.
6. **Многострочный** (`"строка1\nстрока2"`) → `Value` сохраняет обе строки (round-trip `\n`).
7. **Пустой текст**`ArgumentException` (до Add). 8. **Нет видов** → ошибка. 9. **Несуществующий `viewNumber>0`** → ошибка.
Тех. требования:
10. **Задаются + round-trip**: многострочный текст → `lines==2`; `ReadTechnicalRequirementsAsync` возвращает
тот же текст; повторный вызов с другим текстом перезаписывает (read-back = новый, без накопления).
11. **Пустой текст**`ArgumentException`. 12. **Активный документ не чертёж** → понятная ошибка.
## Дальнейшее (вне спека)
- Привязка шероховатости/выносок к геометрии (`BaseObject`); выноски (`Leaders`), обозначения баз
(`Bases`), допуски формы (`Tolerances`); рамка/формат листа; ассоциативная привязка размеров.
@@ -1,129 +0,0 @@
# Дизайн: основная надпись чертежа (drawing_fill_title_block) через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (4 интеграционных + 7 unit, всего 188); ревью Codex спека и реализации учтено
## Правки по ревью реализации Codex
- **null-check ячейки** (#3): доступ к графе через `RequireStampCell(stamp, columnId)` — проверяет
`columnId > 0` и `Text[columnId] != null` с диагностикой; используется в записи и чтении (единое
место валидации, #6).
- **`columnId <= 0`** (#4): валидируется в `RequireStampCell` (защищает и `ReadStampCellAsync`).
- **Нетранзакционность** (#2/#5): записываем все графы, затем один `Update()`; при `Update()==FALSE`
откат старых значений не делаем — зафиксировано комментарием как осознанное поведение (откат
спекулятивен, сценарий невоспроизводим).
- **RCW** — осознанный долг (v2-2), точечный release не вводим.
## Находка спайка: `Str` ПЕРЕЗАПИСЫВАЕТ (Clear не нужен)
Двойная запись в графу 2 («ДЕТАЛЬ.001» → «ИЗДЕЛИЕ.002») → чтение даёт «ИЗДЕЛИЕ.002» (не
дописано). Значит `stamp.Text[id].Str = value` **замещает** содержимое — `IText.Clear()` перед
записью НЕ нужен.
## Правки по ревью Codex
- **Update() результат проверять**: `if (!stamp.Update()) throw`.
- **null-checks**: `LayoutSheets`, `sheet`, `sheet.Stamp` (ранняя диагностируемая ошибка).
- **`RequireActiveDrawing()`** сохраняет проверку `doc is not IKompasDocument2D` (не только
`DocumentType`); `RequireActiveDrawingViews` становится надстройкой над ним.
- **`ColumnId` switch** — дефолт `ArgumentOutOfRangeException`; unit-тест на `(StampField)999`.
- **Тест**: писать и перечитывать ВСЕ три графы (вкл. `name`=графа 1); явный reread `Text[id].Str`;
тест overwrite (запись дважды → читается второе) — уже подтверждён спайком, перенесём в регрессию.
- **`name` = «Наименование изделия» (графа 1, `ksStPartNumber`)** — однозначно в описании (не путать
с `ksStDocumentName=51`).
- **Счётчик результата** = число непустых (trimmed) полей, записанных до успешного `Update()`.
- **Сигнатура**: `FillTitleBlockAsync(string? designation = null, string? name = null,
string? material = null, ct)`.
- **Scale (графа 6) и прочие — ЯВНО вне scope инкремента 2.** Spike-метод и spike-тест удаляются.
- RCW точечно не освобождаем (v2-2).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 2 (оформление). После стандартных видов (`drawing_create_standard_views`)
— заполнить **основную надпись** (штамп, title block) чертежа: обозначение, наименование, материал.
Чертёж с видами + заполненным штампом = презентабельный конструкторский документ. Размеры на видах
— отдельный инкремент 3 (нужен спайк по размерным интерфейсам).
## Спайк: механизм подтверждён вживую (НЕ разучивать)
Спайк (`DrawingStampSpikeTests`, зелёный) подтвердил путь API7 — round-trip графы:
```
IKompasDocument2D doc2d = (IKompasDocument2D)app.ActiveDocument; // активный чертёж
ILayoutSheet sheet = (ILayoutSheet)doc2d.LayoutSheets.ItemByNumber[1]; // лист 1 (1-based)
IStamp stamp = (IStamp)sheet.Stamp;
stamp.Text[columnId].Str = text; // Text[Int32 Id] — индексированное свойство; Id = номер графы
stamp.Update(); // фиксирует
string readBack = stamp.Text[columnId].Str; // чтение для проверки
```
**Проверено:** запись в графу 2 (обозначение) «ДЕТАЛЬ.001» → чтение возвращает «ДЕТАЛЬ.001».
`IText.Str` — read/write; `IStamp.Text[Id]` — индексированное свойство (рефлексия: `get_Text(Int32 Id)`).
`Update()` фиксирует; перестроение документа не нужно.
**Номера граф ГОСТ (форма 1), `ksStampEnum`:** `ksStPartNumber=1` (наименование изделия),
`ksStDescription=2` (обозначение документа), `ksStMaterial=3` (материал), `ksStScale=6` (масштаб),
`ksStAuthor=110` (разработал) и т.д.
## Архитектура
Метод в существующем `DrawingService`. Свой enum `StampField` (паттерн `BasePlane`/`MateType`)
маппится на номера граф — COM-числа не текут в Host/тесты.
- `src/Kompas.Mcp.Core/Drawings/StampField.cs` — enum `StampField` + static `StampFields`
(`ColumnId(field)` → номер графы).
- `DrawingService.FillTitleBlockAsync(designation?, name?, material?, ct)` — реализация.
- Инструмент `drawing_fill_title_block` в `DrawingTools`.
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_fill_title_block` | `designation?`, `name?`, `material?` (все опц.) | Заполнить графы основной надписи активного чертежа: обозначение (графа 2), наименование (1), материал (3). Передаются только нужные поля (null/пусто — пропускается). Возвращает число заполненных граф. |
Контракт: активный документ — чертёж; хотя бы одно поле задано (иначе ошибка). Набор полей —
3 ключевые графы ГОСТ (расширяемо). За один вызов заполняются все переданные (меньше round-trip'ов
для LLM-агента, чем у «одна графа за вызов»).
## Валидация (чистые static, unit-тест)
- `StampFields.ColumnId(StampField)` — `Designation→2`, `Name→1`, `Material→3` (unit-тест маппинга).
- В сервисе: собрать непустые (после `Trim`) поля; если ни одного — `ArgumentException`
«укажите хотя бы одно поле». (Чистый помощник `StampFields.Collect(...)` → список (columnId,text)
— unit-тестируемо без COM.)
## Проверка активного документа
`RequireActiveDrawing()` → `IKompasDocument2D` (тип `ksDocumentDrawing`, иначе раздельная ошибка) —
выделить из существующего `RequireActiveDrawingViews` общий шаг (тот станет надстройкой: вернуть
`.ViewsAndLayersManager.Views`).
## Реализация
`FillTitleBlockCore`: собрать поля (`StampFields.Collect`) → если пусто, ошибка → `RequireActiveDrawing`
→ `sheet = LayoutSheets.ItemByNumber[1]` → `stamp = (IStamp)sheet.Stamp` → для каждого поля
`stamp.Text[columnId].Str = text` → `stamp.Update()` (результат проверить) → вернуть число граф.
Транзитные RCW не освобождаем точечно (долг v2-2, как везде).
## Тестирование
### Unit (`StampFieldsTests`)
- `StampFields.ColumnId` — каждое поле → ожидаемый номер графы (2/1/3).
- `StampFields.Collect` — пропускает null/пусто/пробелы; при всех пустых даёт пустой список;
собирает заданные с обрезкой пробелов.
### Integration (`DrawingStampTests`)
Паттерн: создать чертёж → `FillTitleBlockAsync(...)` → перечитать графы напрямую (helper в сервисе
`ReadStampCellAsync(columnId)` для теста ИЛИ проверка через возврат) → `finally CloseAsync` (база
`IntegrationTestBase`).
1. **Заполнение и чтение**: `designation="ДЕТАЛЬ.001", material="Сталь 45"` → 2 графы; чтение графы
2 == «ДЕТАЛЬ.001», графы 3 == «Сталь 45».
2. **Пустой ввод**: все поля null → `ArgumentException` (ни одной графы не тронуто).
3. **Не-чертёж**: активный документ — деталь → ошибка.
(Для чтения в тесте — добавить `DrawingService.ReadStampCellAsync(columnId)` — он же подтверждает
round-trip; спайк-файл удаляется.)
## Дальнейшее (вне спека)
- Прочие графы (масштаб, разработал, даты, литера), размеры/обозначения на видах (инкремент 3),
рамка/формат листа.
@@ -1,89 +0,0 @@
# Дизайн: отверстие (hole) через API7
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (spike зелёный); на ревью (ревьюер — Codex)
## Цель
Добавить параметрическую операцию **«Отверстие»** (простое цилиндрическое, ksHTBase) — сквозное
или глухое, размещённое на грани по мировой точке. Приоритет 2 из плана.
## Ключевой факт: отверстие — только API7
В **API5 определения отверстия НЕТ** (по рефлексии `Kompas6API5.dll` нет ни одного типа с `Hole`;
есть лишь `Obj3dType.o3d_holeOperation=52` без интерфейса — как было с draft). В **API7**
богатая поддержка: `IModelContainer.Holes3D` (`IHoles3D`) → `Add()``IHole3D`, размещение через
`IHoleDisposal`. Прецедент API7 в слое моделирования уже есть — `move_face` (`FaceEditService`).
Поэтому отдельный сервис `HoleService` (API7), а не метод `PartModeler` (API5).
## Подтверждённый workflow (spike зелёный)
```
IPart7 top = (IPart7)doc3d.TopPart;
IFace face = top.FindObjectsByPoint(x,y,z,true) → первый IFace; // как в move_face
IModelContainer c = (IModelContainer)top; // COM-QI
// Точка размещения центра отверстия в МИРОВЫХ координатах:
IPoint3D pt = c.Points3D.Add();
pt.ParameterType = ksPParamCoord; pt.X=x; pt.Y=y; pt.Z=z; pt.Update();
IHole3D hole = c.Holes3D.Add();
hole.HoleType = ksHTBase; // простое цилиндрическое
hole.Diameter = diameter; // мм
hole.DepthType = throughAll ? ksDTReachThrough : ksDTValue;
if (!throughAll) hole.Depth = depth; // мм
hole.EndFaceType = ksEFFlat; hole.Axis=false; hole.ShowThread=false;
IHoleDisposal d = (IHoleDisposal)hole; // QI
d.BaseSurface = (IModelObject)face; // обязательна грань
d.Perpendicular = true;
d.AssociationVertex = (IModelObject)pt; // центр = созданная точка
d.Direction = true; // подбор по факту (см. ниже)
hole.Update(); doc3d.RebuildDocument(); // проверяем убыль объёма; иначе Direction=false
```
**Направление сверления (важно):** нормаль грани ориентирована непредсказуемо (тот же класс
проблем, что у boss/cut в OPEN_QUESTIONS), а при **сквозном** отверстии `Update()` возвращает
`TRUE` даже когда сверлит «в воздух» (наружу тела → удалено 0 материала). Поэтому направление
подбираем **по факту удаления материала**: строим с `Direction=true`, перестраиваем, сравниваем
объём (API5 `CalcMassInertiaProperties`, согласован с API7-геометрией после `RebuildDocument`);
если объём не убыл — `Direction=false` и повтор; если оба не убавили — ошибка. Только успех
Update недостаточен.
**Размещение** (главная неочевидность, решена): у `IHole3D` нет свойства позиции — оно в
`IHoleDisposal` (`BaseSurface` = грань + `AssociationVertex` = точка/вершина). Самый надёжный
способ задать центр по мировым координатам — создать `IPoint3D` (`ksPParamCoord` + X/Y/Z) и
передать его как `AssociationVertex`. Эскиз размещения НЕ требуется (в отличие от `extrude_cut`).
Единицы: `Diameter`/`Depth`/`X`/`Y`/`Z` — мм. `EndFaceType=ksEFFlat` (плоский торец) для глухого.
## MCP-инструмент (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `hole` | `x,y,z: double`, `diameter: double`, `depth=0`, `throughAll=false` | Просверлить цилиндрическое отверстие на грани, найденной по точке (x,y,z — центр). `throughAll` — сквозное (depth не нужен), иначе глухое на `depth`. Направление в тело — авто (как move_face). |
**Решения (YAGNI):** на первом этапе только `ksHTBase` (простое цилиндрическое); зенковка/
цековка/коническое (`ksHTCounterbore/Countersinking/Conic` + параметрические подынтерфейсы) и
резьба (`ShowThread`/`IThread`) — расширения позже. Глубина «до объекта» (`ksDTObject`) не
выставляется. Отверстие не регистрируется в реестре `_features` (он для API5-операций) — массивы/
зеркало пакета C к нему неприменимы; контракт инструмента возвращает подтверждение без id.
## Реализация
- **`src/Kompas.Mcp.Core/Modeling/HoleService.cs`** (новый): `HoleAsync(x,y,z,diameter,depth,throughAll,ct)`
на STA-потоке; валидация `double.IsFinite` для координат/диаметра/глубины + `>0`; `FindFaceAtPoint`
(копия паттерна из `FaceEditService`); подбор направления по убыли объёма (`TryDirection`);
при неуспехе — откат точки и объекта отверстия (`IFeature7.Delete`), затем `RebuildDocument`.
- **DI:** `AddSingleton<HoleService>()` в `Program.cs`.
- **Инструмент** `hole` в `FeatureTools.cs` (конструктор получает `HoleService`).
- Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
## Тестирование (Integration, `HoleTests`)
1. **Сквозное**: коробка 40×40×20 (центр 0,0; верх Z=20) → `hole(0,0,20, Ø10, throughAll)`
удалён цилиндр π·25·20 ≈ 1570.8 мм³; `InRange(before-after, ±5%)`. ✓
2. **Глухое**: та же коробка → `hole(0,0,20, Ø8, depth=10)` → удалён π·16·10 ≈ 502.7 мм³;
`InRange(before-after, ±10%)` (торец плоский, погрешность модели). ✓
3. **Нижняя грань (подбор направления)**: та же коробка → `hole(0,0,0, Ø10, throughAll)` со стороны
нижней грани → корректное направление внутрь (+Z) подбирается по убыли объёма; удалён π·25·20. ✓
@@ -1,51 +0,0 @@
# Дизайн: типы отверстий — цековка и зенковка (расширение hole)
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (тесты зелёные); на ревью (ревьюер — Codex)
## Цель
Расширить `hole` (простое цилиндрическое, `ksHTBase`) двумя ходовыми типами под крепёж:
**цековка** (`hole_counterbore`, под винт с цилиндрической головкой) и **зенковка**
(`hole_countersink`, под винт с потайной головкой). Через API7 (как базовое отверстие).
## Сигнатуры (рефлексия interop)
```
IHole3D.HoleType = ksHoleTypeEnum: ksHTBase=0, ksHTCounterbore=1, ksHTCountersinking=2, ksHTConic=4
IHole3D.HoleParameters : IKompasAPIObject // приводится к типу по HoleType (после установки HoleType)
ISpotfacingHoleParameters (цековка): SpotfacingDiameter, SpotfacingDepth (мм)
ICountersinkHoleParameters (зенковка): CountersinkType (ksCountersinkTypeEnum), CountersinkDiameter, CountersinkAngle (°), CountersinkDepth
ksCountersinkTypeEnum: ksCTDiameterAngle=0, ksCTDepthAngle=1, ksCTDiameterDepth=2
```
## MCP-инструменты (группа Feature)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `hole_counterbore` | `x,y,z`, `diameter`, `spotfaceDiameter`, `spotfaceDepth`, `depth=0`, `throughAll=false` | Отверстие Ø`diameter` + цилиндрическая цековка Ø`spotfaceDiameter`×`spotfaceDepth` сверху. |
| `hole_countersink` | `x,y,z`, `diameter`, `sinkDiameter`, `sinkAngle`, `depth=0`, `throughAll=false` | Отверстие Ø`diameter` + коническая зенковка Ø`sinkDiameter` под углом `sinkAngle`° (тип `ksCTDiameterAngle`). |
**Решения:** конический тип (`ksHTConic`) и резьба (`ShowThread`/`IThread`) — позже. Зенковка задаётся
парой диаметр+угол (`ksCTDiameterAngle`) — самый привычный ввод.
## Реализация
- **`HoleCore` отрефакторен**: добавлены параметры `ksHoleTypeEnum holeType` и `Action<IHole3D>? configure`
(конфигуратор `HoleParameters` после установки `HoleType`). `HoleAsync``ksHTBase, null`.
Вся остальная логика (размещение через `IHoleDisposal`+`Points3D`, подбор направления по убыли
объёма, откат «сирот») переиспользуется.
- **`CounterboreHoleAsync`**: валидация (`spotDiameter>diameter`, `spotDepth>0`, finite) →
`HoleCore(..., ksHTCounterbore, hole => set ISpotfacingHoleParameters)`.
- **`CountersinkHoleAsync`**: валидация (`sinkDiameter>diameter`, `sinkAngle∈(0;180)`, finite) →
`HoleCore(..., ksHTCountersinking, hole => set ICountersinkHoleParameters: type=ksCTDiameterAngle)`.
- **Инструменты** `hole_counterbore`, `hole_countersink` в `FeatureTools.cs`.
## Тестирование (Integration, `HoleTests`)
- **Цековка**: коробка 40×40×20 → `hole_counterbore(0,0,20, Ø10, spot Ø20×5, throughAll)`
удалено = база (π·25·20≈1571) + кольцо цековки (π·75·5≈1178) ≈ 2749 (`±10%`); и
`removed > базовый цилиндр·1.1` (уширение реально вырезано). ✓
- **Зенковка**: коробка → `hole_countersink(0,0,20, Ø8, sink Ø16, 90°, throughAll)`
`removed > база Ø8 (π·16·20≈1005)` и `< база·2` (конус добавил материал, в разумных пределах;
точный объём зависит от трактовки угла). ✓
@@ -1,342 +0,0 @@
# Дизайн навыка `kompas-fdm-design` — проектирование деталей под FDM-печать
| Поле | Значение |
|------|----------|
| **Статус** | design (учтены 3 ревью: pi/glm-5.1, pi/kimi-k2.6, Codex CLI) |
| **Создан** | 2026-05-27 |
| **Тип** | новый навык (`.claude/skills/kompas-fdm-design/`) |
| **Источники** | консультации pi (`glm-5.1`, `kimi-k2.6`), ревью pi (`glm-5.1`, `kimi-k2.6`) + Codex CLI; прецеденты — навыки `kompas-3d`, `orcaslicer` |
## 1. Цель
Навык-**методика**: как проектировать (и доводить) детали так, чтобы они **хорошо печатались на
FDM/FFF** — без поддержек где возможно, с нужной прочностью на нагрузку, рабочими посадками и
компенсацией особенностей послойной печати. Модель строится через MCP-сервер КОМПАС-3D (этот
проект) с опорой на навык `kompas-3d`; новый навык отвечает на вопрос **«как спроектировать, чтобы
напечаталось»**, а не «чем строить».
## 2. Зафиксированные решения (locked)
1. **Отдельный навык**`kompas-fdm-design`, не сливается в `kompas-3d`.
2. **Без слайсера** — навык **не ссылается** на слайсер и не делает слайс-петлю. Слайсинг вне границ.
3. **Границы = методика DFM + лёгкая самопроверка геометрии** существующими инструментами осмотра
MCP. **Никаких новых MCP-инструментов**, никакого thickness/overhang-солвера.
4. **Универсальный FDM.** Правила **масштабируются** по `w` (ширина линии ≈ диаметр сопла) и `h`
(высота слоя); часть значений — **абсолютные эмпирические мм** (компенсация отверстий, зазоры
посадок, фаски, инсёрты, коробление), они помечены как **калибруемые тестом**. «Параметризация»
не означает, что все числа — формулы от `w`/`h`.
### Принцип проекта соблюдён
`MCP = возможности SDK (чем делать)`, `навык = методика (как делать)`. Правила DFM — методика,
в MCP не идут. Навык опирается на уже реализованные инструменты построения и осмотра.
## 3. Параметризация
- **`w` (ширина линии)** — для горизонтальных размеров/стенок. Дефолт: сопло 0.4 → `w ≈ 0.40.45 мм`.
- **`h` (высота слоя)** — для вертикали и поведения нависаний/мостов. Дефолт `h ≈ 0.5·сопло`
(0.2 мм); структурная печать `h = 0.20.25 мм`.
- **`θ_max` (предельный угол самонесущей поверхности от вертикали)** — **первоклассный параметр**.
Физически `θ_max ≈ arctan(w / 2h)` (≈45° при `w`=0.4, `h`=0.2). **Дефолт 45°** (PLA, хороший
обдув); **40°** для PETG/ABS или толстого слоя (`h≥0.3` → θ_max падает до ~34°). Все правила
нависаний/teardrop/фасок/зенковок берут угол **из `θ_max`**, не хардкодят 45°.
- **Материал** — модификатор: PLA (дефолт, стабилен), PETG (эластичнее, мосты хуже, посадки
«расслабляются», +0.05 мм/сторону к скользящим, θ_max ниже), ABS (усадка/коробление, +зазор,
скругления углов; корпус/обдув — настройки печати, см. границу §12).
- **Калибровочная памятка**: точные числа зависят от калибровки потока/притирки первого слоя/обдува;
навык даёт **разумные дефолты + «проверь печатным тестом»**, а не гарантии.
## 4. Структура файлов
```
.claude/skills/kompas-fdm-design/
├── SKILL.md ← диспетчер (progressive disclosure)
└── references/
├── fdm-rules.md ← полный численный свод DFM
└── geometry-audit.md ← рецепты самопроверки + честные границы
```
### 4.1 `SKILL.md` (диспетчер)
- **Frontmatter**: `name`, `description` с триггерами (§7).
- **Когда применять / связь с `kompas-3d`**: kompas-3d = чем строить; этот навык = как
спроектировать под печать. Слайсер не упоминается.
- **Калибровка**: сопло → `w`; высота слоя → `h``θ_max`; материал → поправки.
- **Два ключевых правила** (§5), включая **рецепт-опрос** (направление нагрузки, косметические/
критичные грани) перед фиксацией ориентации.
- **Рабочий цикл** (§6), **чек-лист печатнопригодности** (§10) — компактно.
- **Ссылки** на `references/`. Если SKILL.md перерастает ~180 строк — выносить детали в references
(держать диспетчер лёгким).
### 4.2 `references/fdm-rules.md` — §8. ### 4.3 `references/geometry-audit.md` — §9.
## 5. Два ключевых правила навыка
1. **Ориентация печати — первое проектное решение.** До эскизов:
- **Спроси у пользователя** (если не задано): главное направление рабочей нагрузки и
косметические/критичные грани. Агент **не выводит путь нагрузки из геометрии** — его задаёт
задача.
- Реши, как деталь стоит на столе: ось Z = направление роста слоёв. От ориентации зависит: где
нависания; куда смотрят отверстия (вертикальные по Z → компенсация диаметра; горизонтальные в
XY → teardrop); путь несущей нагрузки (**держи нагрузку в плоскости XY, вдоль слоёв** —
межслойная прочность по Z ниже, см. §8.6; **Z-сжатие допустимо, Z-растяжение/срез — нет**);
плоскости сопряжения (на XY-гранях верх/низ, не на Z-боковинах); «лесенка» на наклонных/
криволинейных функциональных поверхностях (§8.8); если поддержки неизбежны — чтобы опорные
грани были некритичными/скрытыми.
- Зафиксируй ориентацию и проектируй под неё.
2. **Чек-лист печатнопригодности перед выдачей.** DFM-чек-лист (§10) + лёгкий гео-аудит (§9), и
только потом экспорт. Связка с `kompas-3d`: сначала **`validate_part`** (деталь *валидна*), затем
**FDM-чек-лист** (деталь *печатнопригодна*) — разные проверки. **Гео-аудит эвристический и не
доказывает печатнопригодность** (не ловит путь нагрузки/анизотропию — §9).
## 6. Рабочий цикл
1. **Калибровка**: сопло → `w`; слой → `h``θ_max`; материал → поправки.
2. **Ориентация** (правило 1): опрос (нагрузка/косметика) → постановка на стол, ось слоёв,
плоскости сопряжения, учёт «лесенки» и поддержек.
3. **Правила эскиза/операции** (через `kompas-3d`): стенки `n·w`; нависания → фаски/скос под
`θ_max`; горизонтальные отверстия → teardrop; вертикальные → компенсация диаметра; фаска у
основания; зазоры посадок; заходные фаски; мин. элементы/текст; бобышки/инсёрты/защёлки.
4. **Гео-аудит** (§9).
5. **Предусловия экспорта**: единое тело/манифолд (`boolean_union` при необходимости) →
`validate_part` чисто.
6. **Чек-лист** (§10) → экспорт.
## 7. Триггеры (для `description`)
«сделай деталь печатнопригодной / под FDM», «спроектируй … под печать», «напечатается ли без
поддержек?», «подбери зазоры для печатной посадки», «как ориентировать деталь под печать», «почему
деталь плохо печатается / где будут нависания», «доведи деталь под FDM».
**НЕ для:** механики построения через MCP (это `kompas-3d`); **слайсинга/нарезки/g-code** (вне
границ, отдельный инструмент слайсинга — по имени не называем); поиска по справке SDK (субагент
`kompas-sdk-research`).
## 8. Свод правил DFM (`references/fdm-rules.md`)
> Сведено из двух консультаций (glm-5.1 + kimi-k2.6) и выверено тремя ревью. Где источники
> расходились — взят выверенный дефолт + «калибровать тестом». Числа для `w≈0.40.45`, `h≈0.2`
> (сопло 0.4). **Зазоры — на сторону (радиальные)**; диаметральный = 2× табличного. **Угол нависания
> — от вертикали** (вертикаль=0°, горизонталь=90°); самонесущие — ≤ `θ_max`.
### 8.1 Стенки и оболочки
- Толщина стенки = **`n · w`**. Мин. конструктивная — **2·w (~0.8 мм)**; несущая — **≥3·w**.
- **Маппинг стенка→периметры:** нужно `N` периметров ⇒ стенка **`N·w`** (при `w`=0.45: 3 пер. =
1.35, 4 = 1.8, 5 = 2.25 мм).
- **Не задавай толщину стенки, не кратную `w`** (напр. 0.6 при `w`=0.45): слайсер оставит зазор или
переэкструдирует → наплыв/размер «уехал». Прыгай на следующий кратный.
- **Caveat:** правило `n·w` — для **конструктивных** стенок; если толщина задана внешней
функциональной величиной (флексура, тепловой барьер, посадочный размер) — она важнее кратности.
- Одиночная стенка `1·w` — только декоративная. Узкий сквозной прорез — **≥2·w (~0.8 мм)**.
### 8.2 Нависания, полки, мосты (разделять!)
- **Самонесущие — поверхности ≤ `θ_max` от вертикали.** 45–60° (при дефолте) — печатается с
падением качества; **> `θ_max` существенно — поддержки** → избегать редизайном.
- **90°-полка (консоль, опора с одной стороны) НЕ печатается ни на какой длине** — это не «≤6 мм»,
а ~0 (один слой провисает). Любую горизонтальную полку: **скос под `θ_max`**, либо **превратить в
мост** (две опоры), либо поддержка.
- **Мост (bridge) — пролёт между двумя опорами на одной высоте.** При достаточном обдуве, `h≈0.2`,
консервативно (для ненастроенного слайсера): **PLA ~1525 мм, PETG ~1015 мм, ABS ~1218 мм**.
Длинные прямоугольные проёмы **ориентировать так, чтобы мост шёл по короткой стороне**; концы
моста — на сплошных опорах.
- Функциональную нижнюю поверхность моста закладывать с припуском **0.20.3 мм** на провис.
*Граница:* величину провиса (мм) из CAD не предсказать (зависит от обдува/скорости — настройки
печати); припуск — ориентир, не гарантия.
- **Внутренние/потолочные нависания хуже наружных** (нет обдува) — потолок пазов делать аркой/
шевроном, не плоским пролётом > 2 мм.
- **Доступ к поддержкам:** если поддержка во внутренней полости неизбежна — окно доступа **≥810 мм**.
### 8.3 Отверстия
- **Вертикальные (ось ∥ Z)** печатаются уже номинала → **увеличить диаметр модели** (радиус на
половину): **+0.2 мм (Ø<4)**, **+0.20.3 мм (Ø 410)**, **+0.10.2 мм (Ø>10)**; калибровать,
критичные — рассверливать.
- **Горизонтальные (ось в XY)** → **teardrop** или **D-отверстие** (плоский верх). Мин. Ø **2 мм**.
Круглая часть тоже печатается уже → **+0.10.2 мм** к её Ø.
- **Геометрия teardrop (выверено всеми ревью):** боковины касательны окружности под углом `θ_max`
к вертикали (с двух сторон), сходятся в вершине на вертикальной оси. Высота вершины над центром
= **`r / sin θ_max`**; включённый угол при вершине = **`2·θ_max`**. При `θ_max`=45° →
`r/sin45° = √2·r ≈ 1.414·r` над центром (= **`0.414·r` над верхом окружности**), включённый угол
90°. Низ — оставшаяся дуга окружности. (Проверка вывода: расстояние от центра до боковой прямой,
проходящей через вершину `(0, d)` с направлением `θ_max` от вертикали, равно `d·sin θ_max`;
приравнивая к `r`, получаем `d = r/sin θ_max`.)
- **Глухое отверстие:** дно — внутренний мост; делать **толщину дна ≥2–3 мм** или купольное/
вентилируемое. Сквозные предпочтительнее.
- **Отступ от края:** не «2·Ø», а через **остаточную перемычку** — стенка между отверстием и краем
**≥2–3·w** (лёгкая нагрузка) / больше под крепёж.
### 8.4 Посадки и зазоры (печатная деталь ↔ печатная деталь)
Зазор **на сторону** (радиальный); диаметральный = 2× значения:
| Посадка | Зазор/сторону | Примечание |
|---|---|---|
| **Натяг (press)** | **−0.05…0 мм** (вычесть из номинала!) | короткий, PLA; иначе snap-fit (§8.14) |
| Переходная/плотная | 0.05–0.15 мм | |
| Скользящая | 0.150.20 мм | PLA↔PLA; контакт ≥20 мм → 0.20; PETG +0.05 |
| Свободная | 0.250.35 мм | >0.35/сторону — уже очень слабо |
- **Знак:** «натяг» = **отрицательный** зазор → диаметр вала +/отверстия − относительно номинала.
Положительное число в строке press — ошибка прочтения.
- **ABS↔ABS:** +0.05/сторону. **PETG:** прессовые со временем «расслабляются».
- **Допуск точности (НЕ прибавлять к посадкам):** общий размерный разброс FDM — **XY ±0.2 мм**
(±0.1 на калиброванной), **Z хуже (±0.2…)**. Это точность изготовления, а не добавка к зазору;
критичные сопряжения проектировать на худшую ось.
### 8.5 Первый слой / контакт со столом
- **Elephant foot** — раздутие нижнего периметра от **притирки первого слоя** (низкий Z-offset/
переэкструзия; НЕ от высоты слоя). Снимать **фаской по нижним рёбрам**: **0.3 × 45° (калибровано)**
/ **0.51.0 × 45° (слабая калибровка / сильная притирка)**.
- Внутренние углы у основания — **скругление ≥R0.5**.
- **Опорная площадка:** без «лезвийных» оснований; контакт хотя бы в ~3 периметра. Высокие тонкие
детали — **интегральный фланец 1–2 мм** как геометрия (предпочтительнее всякого brim).
### 8.6 Ориентация и прочность
- **Z (межслойная) прочность от XY:** PLA ~4055%, PETG ~3550%, **ABS ~2035% (выброс)**.
Несущую нагрузку — в **XY (вдоль слоёв)**.
- **Z-сжатие допустимо** (слои в сжатии не расслаиваются); избегать **Z-растяжения и Z-среза**.
- Изгиб: слои в растяжении/сжатии, не на срез по линии слоя.
- Если Z-нагрузка неизбежна — **увеличить несущее сечение** (площадь, работающую на нагрузку)
ориентировочно ×2 относительно XY-расчёта.
- Плоскости сопряжения — на **XY-гранях** (глаже/точнее), не на Z-боковинах.
### 8.7 Минимальные элементы и текст
- Выступ/штифт/ребро — **≥1·w (≥0.5 мм)**; паз/щель — **≥2·w (~0.8 мм)**.
- **Аспект тонких выступов:** высота ≤ ~4–5× базовой ширины; выше — конусность/раскос/редизайн.
- **Выпуклый** текст: штрих **≥0.5 мм**, высота **≥2·h (~0.4 мм)**, sans-serif bold.
- **Гравированный** текст: штрих **≥2·w (~0.80.9 мм)** (нужно ≥2 периметра; 0.6 мм не влезает),
глубина **≥2·h (~0.4 мм)**. (pt не используем — моделируем геометрию в мм.)
### 8.8 «Лесенка» (staircase) — критерий ориентации
- Наклонные/криволинейные поверхности дают ступени слоёв: глубина ≈ **`h / tan(α)`** (α — угол от
горизонтали). Пример: α=30°, `h`=0.2 → ~0.35 мм.
- Критичные (скользящие/уплотняющие/оптические) поверхности **ориентировать вертикально или
горизонтально**, не под малым углом. Это вход в правило ориентации (§5).
- Большой плоский **верх** без опоры коробит («подушка») — внутренние рёбра каждые ~15–20 мм или
достаточная толщина верха.
### 8.9 Бобышки, инсёрты, резьба
- **Саморез/винтонарезной** пилот (M3): ~Ø2.5 PLA / Ø2.6 PETG / Ø2.7 ABS; заход ≥3 мм.
- **Термоинсёрт латунный** (M3): бор **по даташиту** (типично ~Ø4.0, ±0.05); **`Ø_bore = OD_инсёрта
(0…0.1) мм`** (≤ OD, лёгкий натяг под расплав — НЕ больше OD, иначе нет удержания); стенка бобышки
**≥2 мм**; глубина = длина инсёрта + 0.5 мм; **ставить с верхней (Z) грани** (не в боковину).
- **Бобышка под винт (с инсёртом или само-нарезом):** OD ≥ 2–3× Ø винта; не делать массивный
сплошной объём (карман/оболочка).
- **Сквозное под металлический болт:** радиальный зазор 0.2–0.3 → **+0.4–0.6 мм к номиналу** болта.
- **Резьба:** не моделировать <M6 → инсёрты/саморезы. Если моделировать: **≥M6, ось вертикальная**,
зазор +0.1–0.2 мм, профиль крупный/трапецеидальный (не мелкий ISO — вершины-нависания).
### 8.10 Фаски vs скругления
- **Нижние (у стола) рёбра — фаска** (скругление даёт нулевой контакт + EF).
- **Верхние рёбра — скругление** (R0.5–2.0); **но** радиус **> ~½ толщины стенки** сам создаёт зону
нависания > `θ_max` → тогда фаска/ступень.
- **Внутренние углы — всегда скругление ≥R0.5**.
### 8.11 Зенковки / цековки
- **Цековка (counterbore)** — большим Ø/полостью **вверх** (дно по телу, не мостом); глубина +0.3 мм.
- **Зенковка (countersink):** конус **вверх**; при включённом угле **≤90°** стенки ≤45° от вертикали
— печатается; **>90°** стенки становятся нависанием → поддержка или замена цилиндрической цековкой.
(Не путать с «узким» конусом — он печатается, но как посадка под головку бесполезен.)
### 8.12 Заходные фаски (assembly relief)
- На штифтах, отверстиях, инсёртах, защёлках, «ласточкиных хвостах» — **заходная фаска** (≈0.5–1 мм
× 45° или ≈ половина зазора) против задиров при первом контакте/сборке.
### 8.13 Разбиение детали и сборка из печатных частей
- Если оптимальная по прочности ориентация конфликтует с беспод­держечностью, или деталь крупная/
коробится — **разбить на части** с самоустанавливающимися стыками (печатные штифты/шпонки/замки),
склейка; стыки — на XY-гранях. Зазор стыка — по §8.4.
### 8.14 Защёлки (snap-fit) / живой шарнир
- Консольная защёлка: толщина балки **≥2–3·w (≈1.0–2.0 мм)**, зацеп/возврат **0.3–0.8 мм**,
отношение длина/толщина **≥5:1** (до 10:1), **скругление в основании ≥R0.5** (концентратор).
- **Направление слоёв:** балка должна гнуться **в плоскости XY** (слои перпендикулярны изгибу), **не
поперёк Z** (расслоится с первого нажатия).
- **Живой шарнир** — только PLA/PP-подобные, толщина перемычки **0.30.5 мм**; PETG/ABS не годятся.
### 8.15 Коробление (геометрия)
- Большие плоскости (>80×80, особенно ABS): скругления углов R3–5 + рёбра/решётка снизу.
- Радиус внешних углов: R2 (ABS) / R1 (PLA/PETG).
- Длинные тонкие пролёты (>60 мм, <2 мм) — рёбра/косынки каждые 30–40 мм; высота ребра ≤5× базы.
- Избегать сплошных кубов/плит → карман/оболочка + рёбра. Усадка: PLA ~0.3%, PETG ~0.5%, ABS ~0.8%.
- Симметрия геометрии уравновешивает усадку.
- *«Мышиные уши» (Ø8–10 мм пятна по углам)* — **крайняя мера адгезии** (по сути brim-геометрия,
ближе к настройке печати); предпочтительно интегральный фланец/скругления углов.
- *Граница:* стол/корпус/обдув для ABS — **настройки печати**, вне навыка; здесь только геометрия.
### 8.16 Полые детали и общая гигиена модели
- **Полости:** дренаж Ø3–5 мм у **низшей** точки + вент у **высшей**.
- Допуски/зазоры — **в геометрию** (слайсер читает модель буквально, «доводчика» нет).
- Раздельные тела — зазор ≥0.2 мм (это общая CAD-гигиена; перед выдачей объединять — §6 шаг 5).
## 9. Гео-аудит (`references/geometry-audit.md`)
Лёгкая самопроверка инструментами осмотра MCP. **Что проверяемо и границы:**
| Проверка | Как | Статус |
|---|---|---|
| Нависания (приближённо) | `list_faces`/`describe_face`: для **нижних** граней угол поверхности от вертикали; >`θ_max` → флаг | ✅ плоские; ⚠️ криволинейные грубо |
| Ориентация (геом. прокси) | `get_bounding_box`: как ось слоёв соотносится с габаритом | ⚠️ длинная ось ≠ путь нагрузки |
| Горизонтальные круглые отверстия | `describe_face`: цилиндр с горизонтальной осью → «нужен teardrop» | ✅ |
| Номиналы/зазоры/габариты | `measure` между гранями; `get_bounding_box` | ✅ |
| Тело/манифолд перед выдачей | `list_bodies` (одно тело?), `validate_part` | ✅ |
**Граница честности (явно в навыке):**
- **Угол нависания** мерить в **той же конвенции, что §8** (от вертикали; нижняя грань с поверхностью
> `θ_max` от вертикали = нависание) — не путать с углом нормали от горизонтали.
- **Путь нагрузки агент НЕ выводит из габарита** — берёт из задачи/опроса (§5). Длинная ось ≠ несущая.
- **Истинная мин. толщина стенки и полный детект криволинейных нависаний — не решаются** (нет
солвера).
- **Аудит эвристический и НЕ доказывает печатнопригодность** (не ловит анизотропию/путь нагрузки —
главный риск по ревью Codex). Вывод — список флагов для решения, не «приговор». Слайсер навык не
зовёт намеренно.
## 10. Чек-лист печатнопригодности (в `SKILL.md`)
- [ ] Направление нагрузки и косметические грани **получены от пользователя**; ориентация
зафиксирована; нагрузка в XY (или Z только на сжатие); сопряжения на XY-гранях.
- [ ] Стенки кратны `w` (≥2·w; несущие ≥3·w = `N` периметров); нет стенок «не кратных `w`» (кроме
функциональных).
- [ ] Нет 90°-полок; нависания ≤`θ_max` или заменены скосами; мосты в пределах пролёта по короткой
стороне; внутренним поддержкам — доступ.
- [ ] Горизонтальные отверстия — teardrop/D (геометрия из `θ_max`); вертикальные — компенсация Ø;
глухие — дно ≥2–3 мм.
- [ ] Фаска у основания (EF); внутренние углы ≥R0.5; опорная площадка есть.
- [ ] Посадки по таблице со **знаком** (натяг — вычесть); допуск ±0.2 НЕ прибавлен к зазору;
заходные фаски на сопряжениях.
- [ ] Мин. элементы/текст ≥ порогов; аспект тонких выступов ≤4–5×.
- [ ] Бобышки/инсёрты (бор ≤OD, с Z-грани)/резьба/защёлки (изгиб в XY) по правилам.
- [ ] Полости — дренаж/вент; критичные поверхности не под «лесенкой»/поддержкой.
- [ ] Предусловия экспорта: единое тело/манифолд; `validate_part` чисто.
## 11. Обкатка (конвенция проекта)
Приёмы доказываем в `usecases/` (полигон, в .gitignore), затем поднимаем в навык — как с `kompas-3d`.
Кандидаты: **UC «FDM-кронштейн без поддержек»** (с нуля; ориентация/нависания/teardrop/защёлка);
**UC «довести деталь под печать»** (реальный материал — Voron, ср. UC-0002).
## 12. Чего НЕ делаем (YAGNI / non-goals)
- Не зовём слайсер, не делаем слайс-петлю.
- Не добавляем DFM-инструменты в MCP (методика, не возможности SDK).
- Не строим thickness/overhang-солвер (аудит эвристический).
- Не привязываем числа к одному принтеру/материалу.
- Не дублируем механику построения из `kompas-3d`.
- **Не лезем в настройки печати/слайсера** (периметры, заполнение, температуры, **обдув/скорость**,
корпус, brim как настройка, высота первого слоя) — навык про **геометрию**; настройки только как
граница.
## 13. Решения по калибровке / открытые вопросы
- **OQ-1 (решено).** Компенсация Ø вертикальных отверстий: +0.2 (<4), +0.20.3 (410), +0.10.2
(>10); прибавлять **к диаметру** (радиус +X/2); калибровать.
- **OQ-2 (решено).** Elephant foot: 0.3×45° (калибровано) / 0.5–1.0×45° (слабая калибровка); причина
— притирка первого слоя, не высота слоя.
- **OQ-3 (решено).** Экспорт — **через `export_step`** (B-rep, манифолд гарантирован КОМПАС).
`export_stl` в MCP **не вводим** (вне границ навыка).
- **OQ-4 (закрыто).** Ревью прогнаны: pi/glm-5.1, pi/kimi-k2.6, Codex CLI — учтены.
## 14. Дальнейшие шаги
1. ~~Ревью pi + Codex~~ (3 ревью учтены).
2. Self-review спека.
3. **Ревью спека пользователем → отмашка.**
4. `writing-plans` → план реализации (SKILL.md + references/, опц. UC-полигон, синхронизация доков
через субагент `docs-maintainer`).
```
@@ -1,97 +0,0 @@
# Разведка: параметрические эскизы (управление геометрией переменными)
**Дата:** 2026-05-27
**Статус:** РАЗВЕДКА завершена; реализация НЕ начата (крупный отдельный труд — см. вывод)
## Цель разведки
Можно ли сделать так, чтобы переменная модели (`set_variable`, уже есть) управляла геометрией
эскиза — т.е. реализовать параметрические эскизы? Это «завершение» пакета D.
## Главный вывод
**Параметрические эскизы реализуемы ТОЛЬКО через API7 и требуют отдельной новой подсистемы
построения эскизов** (через `FragmentDocument` + `IDrawingContainer` + ограничения
`ksCDimWithVariable`), несовместимой с текущим API5-путём (`ksDocument2D`). Это многосессионный
архитектурный труд, а не один инкремент. В рамках этой сессии НЕ реализуется.
## Что установлено (рефлексия + исследование справки + spike)
### 1. В API5 управляющего размера нет
Справка (`docs/Kompas3D_SDK/interfaces/ограничения.md`, прим. 2): в API5 **нельзя** создать
ограничения **«Размер с переменной» (`ksCDimWithVariable=13`)** и «Фиксированный размер»
(`ksCFixedDim=14`). А именно они нужны, чтобы размер управлял геометрией. `ksDocument2D.ksLinDimension`
создаёт лишь аннотационный (неуправляющий) размер. Значит API5-путь (наш текущий) для параметрики
непригоден.
### 2. Управляющий размер — только через API7 `IParametriticConstraint`
- `IDrawingObject1.NewConstraint()``IParametriticConstraint`; `ConstraintType=ksCDimWithVariable`,
`Variable` (уникальное имя), `Expression`/`Value`, `Create()`. Всё это ЕСТЬ в interop
(`KompasAPI7.dll`: `IDrawingObject1`, `IParametriticConstraint`, `ksConstraintTypeEnum`).
- При установке своего имени переменной надо следить за уникальностью, иначе «Недопустимое имя».
### 3. SPIKE: API5-эскиз через API7 НЕ аннотируется
Проба (`ISketch` последнего эскиза → `ISketch.Curves`) показала **`Curves == null`** для эскиза,
построенного и закрытого через API5 `ksDocument2D`. Значит «достроить параметрику поверх нашего
API5-эскиза» через `ISketch.Curves` НЕ работает.
### 4. Правильный путь — целиком через API7 FragmentDocument
- `ISketch.BeginEdit()`**`FragmentDocument`** (`IKompasDocument2D`).
- `IKompasDocument2D.ViewsAndLayersManager` → вид → `IDrawingContainer`.
- `IDrawingContainer` — типизированные коллекции: `LineSegments`, `Rectangles`, `Circles`, `Arcs`,
`Points`, `Nurbses`, … (`.Add()` создаёт объект-кривую = `IDrawingObject1`).
- На объекте-кривой → `NewConstraint(ksCDimWithVariable)` с именем переменной.
- `ISketch.EndEdit()` → перестроение.
То есть параметрический эскиз строится **другим API** (API7 drawing objects), чем наш текущий
(`PartModeler.Sketch.cs` на API5 `ksDocument2D`). Это новая параллельная подсистема эскизов.
## Объём работ для реализации (оценка)
1. Новый сервис/слой построения эскиза через API7 (`ISketch.BeginEdit``FragmentDocument`
`IDrawingContainer`): создание отрезков/прямоугольников/окружностей через API7-коллекции.
2. Наложение геометрических ограничений (`ksSetObjConstraint`/API7) — фиксация, горизонталь/
вертикаль — чтобы при изменении размера эскиз не «расплывался».
3. Управляющие размеры `ksCDimWithVariable` с именами переменных, увязанные с моделью переменных
(уже есть `VariableService`).
4. Интеграция с существующими формообразующими (выдавить такой эскиз) и реестром `_features`.
5. Эмпирическая проверка ОСТАЁТСЯ открытой: пересчитывает ли решатель эскиза геометрию при
изменении переменной из ВНЕШНЕЙ автоматизации (не проверено — это ключевой риск).
## Решающий spike (API7, выполнен) — ОТРИЦАТЕЛЬНЫЙ результат
Построен полный API7-путь и проверено управление геометрией:
`IModelContainer.Sketchs.Add()``ISketch.Plane = part.DefaultObject[o3d_planeXOY]`
`ISketch.BeginEdit()``FragmentDocument``ViewsAndLayersManager.Views.ActiveView`
(приводится к `IDrawingContainer`) → `LineSegments.Add()` (создаёт `ILineSegment` = `IDrawingObject1`).
Геометрия строится — ОК. Дальше пробовали наложить размер-переменную и проверить, следует ли длина
за переменной `plen` (создана заранее), после `RebuildModel`:
- **`ksCDimWithVariable` (тип 13, «размер с переменной») — `Create()` = FALSE** (и с авто-именем, и
с явным `Variable="plen"`, и с/без `Expression`/`Value`/`SegmentIndex`). Ограничение, ради которого
всё затевалось, через `IDrawingObject1.NewConstraint()` на отрезке эскиза НЕ создаётся.
- **`ksCFixedLenght` (тип 19) — `Create()` = TRUE, НО привязан к литеральному `Value`**, а
`Expression="plen"` игнорируется: после `plen`: 50→80 длина осталась **50** (не 80). `Variable` пуст.
- `ksCFixedPoint`, `ksCFixedLenght` создаются; `ksCDimWithVariable`, `ksCFixedDim` — нет.
**Итог:** через доступные пути COM-автоматизации связать размер эскиза с переменной модели
(чтобы её изменение двигало геометрию) НЕ удаётся: нужный тип ограничения `ksCDimWithVariable` не
строится, а рабочий `ksCFixedLenght` не привязывается к переменной. Вероятно, `ksCDimWithVariable`
требует контекста, недоступного внешней автоматизации (интерактивная простановка размера / иной
порядок создания, не отражённый в справке).
## Рекомендация (обновлено по итогам spike)
Управляемые переменными эскизы через текущий COM-API **признаны нереализуемыми** (`ksCDimWithVariable`
не строится из внешней автоматизации). Не тратить на этот путь время повторно. Возможные направления,
если тема снова станет приоритетной:
- Проверить, не строится ли `ksCDimWithVariable` через **API5** `ksSetObjConstraint` (хотя справка
говорит, что в API5 этот тип недоступен — подтвердить рефлексией/пробой).
- Попробовать создавать размер как самостоятельный объект-аннотацию (не через `NewConstraint` на
отрезке) и затем связывать — но коллекции размеров у `IDrawingContainer` нет (только геометрия).
- Принять, что параметрика ограничена **CRUD переменных** (пакет D, готово) — переменные полезны
для импортированных параметрических моделей и как информационные; управление геометрией из
внешней автоматизации в этой версии КОМПАС/SDK недоступно.
Пакет D = переменные (create/set/delete + чтение), без привязки к геометрии — зафиксировано честно
в документации и описаниях инструментов.
@@ -1,73 +0,0 @@
# Дизайн: параметрика (пакет D) — переменные модели
**Дата:** 2026-05-27
**Статус:** реализовано и проверено (тест зелёный); на ревью (ревьюер — Codex)
## Цель
Дать запись параметрики: **создание, изменение и удаление переменных модели** (`list_variables`
уже читает). Это фундамент управляемых моделей: пользовательские переменные, формулы со ссылками,
внешние переменные для сборок.
## Сигнатуры (рефлексия interop + справка)
```
ksVariableCollection:
object AddNewVariable(string name, double value, string note) // создать (ТОЛЬКО на ksFeature!)
object GetByName(string name, bool testFullName, bool testIgnoreCase)
bool RemoveVariable(string name)
int GetCount(); object GetByIndex(int)
ksVariable:
string name; string Expression; double value; bool external; bool Information; string note
ksPart: bool RebuildModel() // применить изменения переменных
ksFeature: object VariableCollection // СВОЙСТВО (не метод), все переменные детали
```
## Ключевые факты (исследование + эмпирика spike)
1. **Создавать переменные можно только на коллекции корневого `ksFeature`**
(`ksPart.GetFeature().VariableCollection`), НЕ на `ksPart.VariableCollection()` (та — только
внешние). `AddNewVariable` на ksPart-коллекции не создаёт пользовательскую переменную.
2. **`Expression` — ведущее поле**, `value` — вычисленный результат. Менять значение надо через
`Expression` (константа «30» или формула «width*2»); прямое `value=` ненадёжно (перезапишется).
3. **Изменения вступают в силу после `ksPart.RebuildModel()`**; `value` пересчитывается из
`Expression`. RCW переменной после RebuildModel «застревает» на старом значении — **перечитывать**
переменную из свежей коллекции (`ReadValueFresh`).
4. **`list_variables` исправлен**: читал `ksPart.VariableCollection()` (только внешние) → теперь
`ksPart.GetFeature().VariableCollection` (все, включая созданные), с фолбэком. Иначе созданная
переменная не была бы видна агенту.
5. **Удаление**: нельзя удалить переменную, на которую ссылаются другие (RRemoveVariable→FALSE);
удалять в порядке зависимостей.
6. **ОГРАНИЧЕНИЕ (важно для контракта):** переменная управляет геометрией только в
**параметрической** модели (размеры эскизов связаны с именами переменных). Наши эскизы строятся
литеральными координатами — у них нет именованных размеров, поэтому set_variable хранит/считает
значение, но геометрию не меняет. Привязка размеров эскиза к переменным (параметрические эскизы)
— отдельный большой пласт (будущее). Поэтому тест проверяет CRUD/вычисление через чтение, а не
изменение геометрии.
## MCP-инструменты (новая группа, `VariableTools`)
| Инструмент | Параметры | Поведение |
|---|---|---|
| `create_variable` | `name`, `value`, `note?`, `external=false` | Создать переменную. Возвращает значение. |
| `set_variable` | `name`, `expression` | Задать выражение (константа/формула), перестроить, вернуть значение. |
| `delete_variable` | `name` | Удалить (нельзя, если есть зависимые). |
Чтение — существующий `list_variables` (теперь видит все переменные).
## Реализация
- **`src/Kompas.Mcp.Core/Modeling/VariableService.cs`** (новый): `CreateVariableAsync`,
`SetVariableAsync`, `DeleteVariableAsync`, `GetVariableValueAsync` (read-back) на STA-потоке.
Хелпер `RootVariables()` → (`ksPart`, feature-коллекция); `ReadValueFresh(name)` после RebuildModel.
Валидация: имя не пусто, value конечно, выражение не пусто.
- **`VariableTools.cs`** (новый): 3 инструмента; DI `AddSingleton<VariableService>()`.
- **`ModelInspectionService.ReadVariables`**: источник → feature-коллекция (фолбэк на ksPart).
- Транзитные RCW не освобождаем точечно — консистентно (долг v2-2).
## Тестирование (Integration, `VariableTests`)
CRUD round-trip: построить коробку (дерево построения) → `create_variable("width",40)` (значение 40;
видна в `list_variables`) → `set_variable("width","12+8")` → 20 → создать `height`, `set "width*2"`
40 (формула со ссылкой) → удалить `height`, затем `width` (порядок зависимостей) → обе исчезли
(`GetVariableValueAsync` бросает `KeyNotFoundException`). ✓
@@ -1,119 +0,0 @@
# Дизайн: линия-выноска с текстом (drawing_add_leader) через API7
**Дата:** 2026-05-28
**Статус:** дизайн согласован, спайк проведён, ревью Codex + pi/glm-5.1 + pi/kimi-k2.6 учтено — к реализации
## Правки по ревью реализации (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- Проверка возврата `AddBranchByPoint`/`SetBranchTextPosition` (оба `bool`) — ранняя диагностика
вместо позднего `RPC_E_SERVERFAULT` (Codex, glm).
- RU-алиасы в сообщении `ShelfDirections.Parse` и в `[Description]` инструмента (glm, kimi).
- Интеграционный тест направления полки параметризован `Right/Left/Up/Down` — покрывает всю
поверхность `ToKompas` на реальном COM (glm, kimi).
- Отклонено (обоснование): unit-тест `ToKompas` (граница проекта — тест не ссылается на
`Kompas6Constants`); e2e-тест tool-обёртки (тонкая, `Parse`+сервис покрыты); `angleDeg`
(у `ILeader`/`IBaseLeader` нет свойства Angle — ориентация геометрическая); выравнивание default
`text` у `AddText` (вне объёма; required-контракт `AddLeader` корректнее).
## Правки по ревью спека (Codex + pi/glm-5.1 + pi/kimi-k2.6)
- **Сигнатура** `DrawingValidation.RequireNonEmptyText(text, nameof(text))` (двухаргументный — как есть).
- **QI с диагностикой:** `bl as IBranchs ?? throw`, `bl as ILeader ?? throw` (не прямой cast); read-back
`leader.TextOnShelf?.Str ?? string.Empty`; двухшаговый null-check `Leaders` (коллекция) и `Add()`
как `AddRoughCore`.
- **`ShelfDirections.ToKompas(Auto)` бросает `ArgumentOutOfRangeException`** (Auto обрабатывается в сервисе
ДО вызова — «не задавать ShelfDirection»; прямой вызов для Auto — ошибка). RU-алиасы `Parse`:
`авто/вправо/влево/вверх/вниз`.
- **Вырожденная выноска:** `RequireDistinctPoints(x, y, textX, textY)` (остриё ≠ полка; есть валидатор).
- **Критический порядок** (комментарий в коде): `AddBranchByPoint` ДО `SetBranchTextPosition`/`Update`
(иначе RPC_E_SERVERFAULT — выноска без ветвей).
- **Тесты:** пустой текст — `""` И `" "`; `viewNumber=0` резолвится в первый вид; путь `shelfDirection=auto`
(ShelfDirection не задаётся). XML-`<summary>` членов `ShelfDirection` — «в ЛОКАЛЬНОЙ СК вида».
- Нейминг COM-объекта — `baseLeader` (не `bl`). `GetViewLeaderCountAsync` считает ВСЕ подтипы выносок
(для v1 ок; фильтр — при добавлении ksDrPosLeader/… — в «Дальнейшее»).
## Цель
Веха 2D-ЧЕРТЁЖ, инкремент 9. Поставить на виде **линию-выноску** (leader) с текстовой надписью на полке —
поясняющее обозначение, указывающее стрелкой на элемент (примечание, материал детали, и т.п.).
## Спайк: последовательность подтверждена вживую (НЕ разучивать)
Спайк (`_SpikeLeader`, прогнан на реальном КОМПАС, затем удалён). **Ключ:** только что созданная
выноска имеет **0 ответвлений** — любые операции над ответвлением (`SetSignPosition`) и `Update` на
выноске без ветвей падают с `RPC_E_SERVERFAULT`; сначала нужно добавить ответвление через `IBranchs`.
```
IBaseLeader bl = symbols.Leaders.Add(DrawingObjectTypeEnum.ksDrLeader); // ksDrLeader=20
((IBranchs)bl).AddBranchByPoint(0, x, y); // QI; ответвление 0 — остриё (куда указывает стрелка)
bl.SetBranchTextPosition(textX, textY); // положение полки/текста
((ILeader)bl).TextOnShelf.Str = text; // надпись на полке (.Str — наш interop)
// опц.: ((ILeader)bl).ShelfDirection = ksShelfDirectionEnum.ksLSRight;
bl.Update(); // True, bl.Valid == True
```
**Проверено:** `AddBranchByPoint(0,x,y)``BranchCount=1`; `Update=True`, `Valid=True`,
`TextOnShelf.Str` round-trip; `Leaders.Count` +1. Работает с явной `ShelfDirection` и без неё.
`BaseLeader` реализует `IBaseLeader`/`IDrawingObject`; `IBranchs` и `ILeader` — COM-QI от него.
Интерфейсы: `ILeaders.Add(DrawingObjectTypeEnum)``BaseLeader` (для обычной выноски `ksDrLeader=20`);
`IBranchs.AddBranchByPoint(int index, double x, double y)`; `IBaseLeader.SetBranchTextPosition(x,y)` /
`Update` / `Valid` / `Delete` / `ArrowType` (`ksArrowEnum`); `ILeader.TextOnShelf` (`IText`),
`ShelfDirection` (`ksShelfDirectionEnum`: `ksLSNone=0`/`ksLSRight=1`/`ksLSUp=2`/`ksLSDown=3`/`ksLSLeft=-1`).
## MCP-инструмент
| Инструмент | Параметры | Поведение |
|---|---|---|
| `drawing_add_leader` | `x,y` (остриё — куда указывает стрелка), `textX,textY` (полка/якорь текста), `text`, `shelfDirection="auto"` (auto\|right\|left\|up\|down), `viewNumber=0` | Поставить выноску на виде: стрелка из полки указывает в (x,y), текст `text` на полке у (textX,textY). Координаты — ЛОКАЛЬНАЯ СК вида (мм). `shelfDirection` — направление полки (auto = по умолчанию КОМПАС). Возвращает текст (read-back) и номер вида. |
## Архитектура
В `DrawingService` (namespace `Kompas.Mcp.Core.Drawings`).
- `AddLeaderAsync(viewNumber, x, y, textX, textY, text, shelfDirection, ct)``DrawingAnnotationResult`
(переиспользуем: `Value`=read-back текст, `ViewNumber`).
- Использует существующий `RequireSymbols2DContainer(viewNumber)` (выноски в `ISymbols2DContainer.Leaders`).
- Новый файл `src/Kompas.Mcp.Core/Drawings/ShelfDirection.cs`: `enum ShelfDirection {Auto,Right,Left,Up,Down}`
(XML-`<summary>` на членах) + `ShelfDirections.Parse(string)` / `ToKompas(ShelfDirection)→ksShelfDirectionEnum`
по образцу `AngleDimensionTypes`. `Auto` → не задавать `ShelfDirection` (оставить дефолт КОМПАС).
- Счётчик `GetViewLeaderCountAsync(viewNumber)` (для теста попадания в вид).
## Реализация
`AddLeaderCore`: `RequireNonEmptyText(text)` + `RequireFiniteCoords(x,y,textX,textY)`
`RequireSymbols2DContainer``symbols.Leaders` (null-check) → `.Add(ksDrLeader)` (null-check) →
**внутри `try/catch`+`Delete`**: `((IBranchs)bl).AddBranchByPoint(0, x, y)` (QI null-check),
`bl.SetBranchTextPosition(textX, textY)`, `((ILeader)bl).TextOnShelf` (null-check) `.Str = text`,
если `shelfDirection != Auto`: `((ILeader)bl).ShelfDirection = ShelfDirections.ToKompas(...)`
`bl.Update()` (FALSE → откат) → `bl.Valid` (false → откат) → read-back `((ILeader)bl).TextOnShelf.Str`
`DrawingAnnotationResult{Value=read-back, ViewNumber}`.
RCW точечно не освобождаем (консистентно; долг v2-2).
## Валидация (чистые static, unit)
- `RequireNonEmptyText(text, nameof(text))`, `RequireFiniteCoords(x,y,textX,textY)`,
`RequireDistinctPoints(x, y, textX, textY)` (остриё ≠ полка) — все уже есть.
- `ShelfDirections.Parse``auto/right/left/up/down` (+ рус. `авто/вправо/влево/вверх/вниз`, регистр/пробелы);
неизвестное → `ArgumentException`; null → `ArgumentNullException`. `ToKompas` (→ `ksShelfDirectionEnum`)
НЕ покрываем unit (граница проекта); бросает `ArgumentOutOfRangeException` на `Auto`.
## Тестирование
### Unit (`ShelfDirectionsTests`)
- `Parse`: `auto/right/left/up/down` (+ регистр, рус.) → enum; неизвестное → `ArgumentException`; null → `ArgumentNullException`.
### Integration (`DrawingLeaderTests`; модель — коробка + стандартные виды)
1. **Ставится + round-trip + viewNumber=0**: `text="Примечание 1"`, `shelfDirection=auto`, `viewNumber=0`
`Value=="Примечание 1"` (read-back), `viewNumber=0` резолвится в первый вид (`r.ViewNumber` совпал),
`Leaders.Count` целевого вида +1.
2. **shelfDirection=left** (не-auto путь) на целевом виде → ставится, `Leaders.Count` +1.
3. **Пустой текст** (`""` и `" "`) → `ArgumentException` (до Add).
4. **Вырожденная** (остриё совпадает с полкой) → `ArgumentException` (до Add).
5. **Нет видов** → понятная ошибка. 6. **Несуществующий `viewNumber>0`** → понятная ошибка.
## Дальнейшее (вне спека)
- Многострочный текст полки / текст под полкой (`TextUnderShelf`); несколько ответвлений
(`AddBranchByPoint` с разными индексами); привязка к геометрии (`IBranchs.SetBaseObject`); тип стрелки
(`ArrowType`); специальные выноски (позиция/клеймо/маркер: `ksDrPosLeader/ksDrBrandLeader/ksDrMarkerLeader`).
Базы (`Bases`), допуски формы (`Tolerances`).
@@ -1,96 +0,0 @@
# Каталог внешнего CAD-контракта для агента — дизайн
## Цель
Создать отдельный документ `docs/AGENT_CAD_TOOL_CATALOG.md`, который описывает желаемый внешний MCP-контракт управления CAD с точки зрения автономного агента, показывает все реализованные и необходимые нереализованные методы и позволяет оценить полноту контракта по сквозным CAD-сценариям.
## Границы
- Каталог описывает внешний контракт, а не внутреннюю реализацию.
- COM-интерфейсы, вызовы API5/API7 и другие детали SDK КОМПАС-3D в итоговую таблицу не включаются.
- SDK разрешено использовать только для внутренней оценки реалистичности предлагаемых методов и выявления платформенных ограничений.
- Методы формулируются как общие CAD-операции, а не как инструменты под отдельную пользовательскую задачу.
- В каталог входят все существующие MCP-инструменты и все выявленные методы, необходимые для полноты агентского контура.
## Структура итогового документа
1. Назначение документа и определение полноты внешнего контракта.
2. Легенда статусов и приоритетов.
3. Сводная матрица покрытия сквозных сценариев.
4. Таблицы методов по функциональным доменам.
5. Приоритизированный перечень пробелов.
6. Общий вывод о текущей полноте контракта.
Функциональные домены:
- сессия и документы;
- 2D-геометрия и эскизы;
- 3D-моделирование;
- прямое и историческое редактирование;
- инспекция, выбор объектов и измерения;
- сборки;
- чертежи и оформление;
- импорт и экспорт;
- управление состоянием, восстановление и надёжность агентской работы.
## Формат таблиц методов
Каждая строка описывает один метод внешнего контракта. Обязательные столбцы:
| Столбец | Содержание |
|---|---|
| Метод | Стабильное имя MCP-метода в `snake_case` |
| Назначение | Краткое описание результата метода с позиции вызывающего агента |
| Статус | `✅ реализован`, `🟡 частично`, `⬜ не реализован` или `⛔ ограничен платформой` |
| Необходимость | `Core`, `Advanced` или `Optional` |
| Пробел / ограничение | Что отсутствует в контракте или какая часть поведения не покрыта |
SDK-интерфейсы, классы сервисов и другие детали реализации в эти таблицы не добавляются.
## Критерии классификации
### Статус
- `✅ реализован` — метод существует в текущем MCP-каталоге и предоставляет заявленное внешнее поведение.
- `🟡 частично` — метод существует, но покрывает только часть необходимого внешнего поведения или поддерживает ограниченный набор вариантов.
- `⬜ не реализован` — метод нужен целевому контракту, но отсутствует как MCP-инструмент.
- `⛔ ограничен платформой` — желаемое поведение невозможно или ненадёжно в доступном Automation API; ограничение должно быть сформулировано на уровне внешнего результата без SDK-подробностей.
### Необходимость
- `Core` — без метода агент не может надёжно завершить базовый сквозной сценарий либо проверить результат мутации.
- `Advanced` — метод нужен для промышленно значимого расширенного сценария, но не блокирует минимальный цикл моделирования.
- `Optional` — повышает удобство, производительность или широту применения, сохраняя работоспособность основного контура без него.
### Приоритет пробела
- `P0` — разрыв базового сквозного сценария или отсутствие необходимой обратной связи/управления состоянием.
- `P1` — существенное ограничение распространённого профессионального сценария.
- `P2` — расширение охвата или удобства без разрыва основных сценариев.
## Оценка полноты
Полнота оценивается не числом методов, а способностью агента выполнить замкнутый цикл `обнаружить состояние → изменить модель → проверить результат → сохранить или безопасно откатить`.
Сводная матрица должна проверить минимум следующие сценарии:
1. Создать и сохранить параметрическую 3D-деталь.
2. Открыть или импортировать модель, локально изменить геометрию и проверить результат.
3. Создать сборку, разместить компоненты, наложить сопряжения и проверить структуру.
4. Создать комплект основных видов чертежа, оформить размеры и обозначения, сохранить результат.
5. Выполнить геометрическую и документную инспекцию без обязательного визуального анализа.
Для каждого сценария фиксируются покрытые этапы, блокирующие пробелы и итоговая оценка: `полный`, `частичный` или `неполный`.
## Проверка реалистичности
Перед включением нереализованного метода в контракт проверяется, что требуемое поведение в принципе доступно в установленной версии КОМПАС-3D либо может быть составлено из надёжных операций. Если реалистичность не подтверждена, метод получает статус `⛔ ограничен платформой` или явную пометку о необходимости технического исследования; детали исследования остаются вне итогового каталога.
## Критерии готовности
- Все текущие MCP-инструменты представлены ровно по одному разу.
- Для каждого домена перечислены необходимые отсутствующие методы.
- Частично реализованные методы не ошибочно помечены как полностью реализованные.
- Сквозные сценарии позволяют увидеть блокирующие пробелы независимо от общего количества методов.
- Итоговый список `P0P2` согласован с доменными таблицами и не содержит методов, отсутствующих в основном каталоге.
- Документ не содержит внутренних COM/SDK-деталей.
@@ -1,75 +0,0 @@
# Codex SDK Research Agent Design
## Goal
Adapt the project-scoped `kompas-sdk-research` custom agent for Codex while
leaving the Claude Code agent unchanged. The Codex agent must be optimized for
fast, read-heavy searches in the local KOMPAS SDK Markdown knowledge base and
must not modify the workspace.
## Configuration
The Codex agent remains in `.codex/agents/kompas-sdk-research.toml` and uses:
```toml
name = "kompas-sdk-research"
model = "gpt-5.6-terra"
model_reasoning_effort = "medium"
sandbox_mode = "read-only"
```
`gpt-5.6-terra` fits exploration, documentation scans, and distilled support
work. Medium reasoning is used because SDK questions can require correlating
several interfaces, overloads, constants, and COM call-chain details.
## Agent Responsibilities
The agent searches only local project and installed SDK reference material:
- `docs/Kompas3D_SDK/` is the canonical source for COM API5/API7 documentation.
- SDK headers under the installed KOMPAS SDK may be read when exact constant
values need confirmation.
- The agent must not browse the web, edit files, implement features, or build
geometry in KOMPAS-3D.
- KsAPI is outside the Markdown knowledge base and must be reported as outside
scope instead of guessed.
The parent agent delegates non-trivial SDK lookups before implementation. It
remains responsible for validating interop-specific discrepancies through
reflection over `libs/kompas-interop/*.dll` when needed.
## Prompt Adaptation
Remove Claude-specific references to Haiku and Opus from the Codex TOML. Keep
the existing domain guidance, but express it in provider-neutral Codex terms.
The description must make proactive delegation triggers clear without claiming
that the agent uses tools or model behavior specific to Claude Code.
The response contract remains concise and evidence-oriented:
1. Relevant interface, method, property, enum, or constant.
2. Automation syntax and COM syntax when parameter ordering matters.
3. Parameter and return-value meanings, including units and side effects.
4. Related overloads or interfaces when relevant.
5. Source article path and its `sources` frontmatter entry.
6. A warning when generated .NET interop names may differ from SDK docs.
## Isolation and Compatibility
The Claude definition in `.claude/agents/kompas-sdk-research.md` is not changed.
The two versions intentionally use different configuration formats and model
selection while sharing the same research methodology.
The Codex agent's read-only behavior is enforced by `sandbox_mode`, not merely
requested in prose. No exact Claude-style tool allowlist is copied into the
Codex configuration.
## Verification
After editing the TOML:
- parse it as TOML;
- confirm the required fields and selected model settings;
- confirm `sandbox_mode = "read-only"`;
- search the Codex file for stale `Haiku`, `Opus`, and Claude-specific wording;
- confirm the Claude agent file is unchanged.
@@ -1,328 +0,0 @@
# Спек: упаковка kompas3d-mcp в плагин Claude Code и публикация через Gitea
Дата: 2026-07-31 · Статус: согласовано, ревизия 2 (после ревью Codex)
## 1. Цель
Сделать так, чтобы возможности этого проекта (MCP-сервер КОМПАС-3D + методические навыки)
устанавливались в чужой Claude Code одной командой, а не воспроизведением ручной настройки:
клонировать репозиторий, собрать `.NET`, прописать абсолютный путь в `.mcp.json`, скопировать навыки.
Аудитория — автор и узкий круг знакомых, с прицелом на возможную публичность. Отсюда: структура и
README сразу рассчитаны на постороннего, но вылизанный онбординг (мастера установки, автодиагностика
всех отказов) в объём не входит.
**Вне объёма этой спеки:** сценарий выполнения задач в репозитории (рабочий цикл доработки MCP) —
прорабатывается отдельно и с нуля; `docs/superpowers/NEXT-SESSION.md` пока остаётся как есть.
## 2. Исходные факты (проверены)
- `git.shahovalov.ru/mikhail/claude-plugins` **редиректит** на `home-repo-cc` — репозиторий
переименован. Каталог плагинов = `home-repo-cc` (`.claude-plugin/marketplace.json`, плагин
`obsidian-autodoc`).
- `home-repo-cc`**приватный** (Gitea API отдаёт 404 без токена). `kompas3d-mcp`**публичный**,
релизы включены.
- Marketplace поддерживает source-тип **`git-subdir`** (`{url, path, ref?, sha?}`, разрежённый клон).
- Плагин объявляет MCP-серверы через `.mcp.json` в своём корне; в путях доступна
`${CLAUDE_PLUGIN_ROOT}`. Плагин копируется в кеш `~/.claude/plugins/cache`; **версия из
`plugin.json` служит ключом кеша** — при совпадении версии `/plugin update` и автообновление
плагин пропускают.
- `/plugin marketplace update` обновляет **каталог**, а не установленный плагин; плагин обновляется
`/plugin update`.
- **Фоновое автообновление каталога отключает git-credential-helper**, поэтому для приватного
HTTPS-каталога фоновый `git pull` не аутентифицируется и Claude Code уходит в ре-клон (который
может отваливаться по 120-секундному таймауту). SSH-ремоуты этим не затронуты.
- `.agents/skills/` в этом репозитории — побайтово идентичная копия `.claude/skills/` (untracked):
дублирование навыков между харнессами уже началось вручную.
- Навык `kompas-3d` **устарел и не самодостаточен**: заявляет 46 инструментов (в CLAUDE.md — 83),
ссылается на `usecases/`, `src/`, субагентов `kompas-sdk-research` и `docs-maintainer`, которых в
плагине не будет.
- Сервер: `net8.0-windows`, x64, вендорские interop-DLL АСКОН в `libs/kompas-interop` (4.4 МБ,
в репозитории, `Private=true` → копируются в вывод publish). Ни WinForms, ни WPF не используются.
- Отдельного `LICENSE` в репозитории нет; README говорит лишь «распространяются согласно условиям
АСКОН».
## 3. Принятые решения
| Вопрос | Решение |
| --- | --- |
| Аудитория | автор + знакомые; структура «как для чужого», публичность — потом |
| Источник бинаря | готовый `win-x64` из Gitea Release, собирается CI |
| Состав плагина | навыки `kompas-3d`, `kompas-fdm-design`; команда `/kompas:doctor`; MCP-сервер |
| Каталог | остаётся приватным `home-repo-cc`, доступ знакомым выдаётся в Gitea |
| Раскладка | плагин лежит в `kompas3d-mcp/plugin/`, каталог ссылается через `git-subdir` |
| Имя плагина | `kompas` |
| Канал дистрибуции | ветка **`dist`**, а не `main` (см. §6) |
| Навыки | публикуется **пользовательская редакция**; материал по разработке сервера выносится (см. §5) |
`kompas`, а не `kompas-3d`: пространство имён даёт `/kompas:doctor`, навыки становятся
`kompas:kompas-3d` / `kompas:kompas-fdm-design`, и это совпадает с именем MCP-сервера (`mcp__kompas__*`).
Субагент `kompas-sdk-research` и справка SDK **в плагин не входят** — это инструмент
разработки сервера, а не построения деталей. (С 2026-07-31 база и вовсе вне репозитория:
вынесена в `kompas-sdk-docs` и доступна через RAG — см.
[спек выноса](2026-07-31-sdk-docs-rag-migration-design.md). Строка `docs/Kompas3D_SDK`
в списке `forbidden` теста остаётся страховкой.)
## 4. Раскладка
```
kompas3d-mcp/
plugin/ ← весь плагин, единственный источник истины навыков
.claude-plugin/plugin.json
skills/kompas-3d/SKILL.md ← пользовательская редакция
skills/kompas-fdm-design/{SKILL.md,references/}
commands/doctor.md → /kompas:doctor
.mcp.json
scripts/launch-kompas-mcp.ps1
server.lock.json
README.md
adapters/{codex,opencode}/
.claude/skills/kompas-mcp-dev/ ← материал по разработке сервера (не публикуется)
tools/sync-agent-assets.ps1
.gitea/workflows/{ci.yml,release.yml}
```
`plugin/.claude-plugin/plugin.json`: `name: "kompas"`, `displayName`, `description`, `version`
(семвер, поднимается релизным коммитом), `author`, `homepage`, `repository`, `license`, `keywords`.
## 5. Навыки: пользовательская редакция и источник истины
Публиковать нынешний `kompas-3d` нельзя: он написан для работы **внутри этого репозитория**.
Перед первым релизом навык разделяется:
- `plugin/skills/kompas-3d/SKILL.md`**пользовательская редакция**: только методика построения
через MCP-инструменты. Убираются ссылки на `usecases/`, `src/`, субагентов и предписание
«заведи кейс и продуктизируй инструмент»; список инструментов приводится в соответствие с
фактическим (сверять по `README.md` §«Инструменты», не по памяти).
- `.claude/skills/kompas-mcp-dev/SKILL.md` — всё вынутое (полигон `usecases/`, продуктизация
инструментов, субагенты). Это **перевалочный пункт**: доводка принадлежит отложенной работе по
рабочему циклу репозитория, здесь только сохраняем материал, не теряя его.
`kompas-fdm-design` переносится как есть (он опирается на `kompas-3d` и `export_step`, внешних
ссылок на репозиторий не содержит) — проверить это отдельным чтением перед переносом.
Навыки хранятся **только** в `plugin/skills/`. `tools/sync-agent-assets.ps1` создаёт junction'ы
`.claude/skills/<name>` и `.agents/skills/<name>``plugin/skills/<name>`:
- junction на Windows создаётся без прав администратора и может указывать на другой локальный том;
- **если junction создать не удалось — скрипт завершается ошибкой**, а не молча копирует: иначе
«единственный источник истины» превращается в две расходящиеся копии. Копия допустима только
явным флагом `-AllowCopy`, и тогда `sync -Check` обязан сверять хеши;
- `.gitignore` пополняется **точечными путями** (`/.claude/skills/kompas-3d/`,
`/.claude/skills/kompas-fdm-design/`, те же в `.agents/`), а не каталогами целиком: git видит
junction как обычный каталог и без ignore проиндексирует содержимое повторно;
- скрипт никогда не удаляет рекурсивно то, что не является созданным им junction'ом; удаление
делается без следования в target;
- при чекауте внутри OneDrive-дерева поведение не гарантируется — README рекомендует держать
репозиторий вне синхронизируемой папки.
Для локальной работы в этом репозитории плагин **не устанавливается** — иначе навыки задвоятся.
## 6. Канал дистрибуции: ветка `dist`
Каталог **не** может ссылаться на `main`: релиз собирается из отмеченного тегом коммита A, а
бот-коммит с обновлённым `plugin.json` попадёт в текущую голову `main` (уже коммит B). Пользователь
получил бы содержимое `plugin/` из B под версией, чей бинарь собран из A.
Решение: релизный workflow формирует ветку **`dist`** — коммит **строго поверх отмеченного тегом
коммита**, добавляющий только `server.lock.json` и `version` в `plugin.json`. Каталог ссылается на
`ref: "dist"`. `main` остаётся веткой разработки, релизных коммитов в неё нет.
`server.lock.json` фиксирует и происхождение сборки:
```json
{ "version": "1.0.0",
"sourceSha": "63bd9db…",
"url": "https://git.shahovalov.ru/mikhail/kompas3d-mcp/releases/download/v1.0.0/kompas-mcp-1.0.0-win-x64.zip",
"sha256": "…" }
```
## 7. Лаунчер
`plugin/.mcp.json`:
```json
{ "mcpServers": { "kompas": {
"command": "powershell",
"args": ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File",
"${CLAUDE_PLUGIN_ROOT}/scripts/launch-kompas-mcp.ps1"] } } }
```
Алгоритм `launch-kompas-mcp.ps1`:
1. `$ErrorActionPreference='Stop'`, `$ProgressPreference='SilentlyContinue'`; весь скрипт обёрнут в
`try/catch`, где `catch` пишет причину в stderr и делает `exit 1`.
2. Задан `KOMPAS_MCP_EXE` → использовать его (цикл разработки: локальная сборка вместо релиза).
3. Иначе целевой каталог `%LOCALAPPDATA%\kompas-mcp\<version>`; установка считается годной, только
если рядом лежит **маркер** `install.json` с совпадающими `version` / `sha256` / `sourceSha`
и присутствуют все обязательные файлы. Одного `Test-Path kompas-mcp.exe` недостаточно —
иначе повреждённая или оборванная установка запускается молча.
4. Установка отсутствует → захватить именованный mutex по версии; после захвата **повторно
проверить** установку (гонку мог выиграть сосед). Скачать во временный каталог
`%LOCALAPPDATA%\kompas-mcp\.tmp\<guid>` — тот же том, иначе переименование каталога не атомарно;
посчитать SHA256; **несовпадение — отказ с ненулевым кодом, скачанное не запускается**;
распаковать, проверить состав, записать маркер, переименовать в целевой каталог. Ошибка
`destination exists` трактуется как успех соседа. Временный каталог удаляется в `finally`.
5. Запуск и проброс кода — точным шаблоном:
```powershell
& $exe @args
$serverExit = $LASTEXITCODE
exit $serverExit
```
`powershell -File` возвращает 0, если скрипт не сделал `exit` явно, — без этого падение сервера
останется невидимым.
**Инвариант stdout:** bootstrap-часть не производит **ни одной** строки в success stream (stdout —
канал JSON-RPC). Запрещён не только `Write-Host`: результаты `Get-Item`, `New-Item`,
`ConvertFrom-Json`, `Get-FileHash`, `Expand-Archive` присваиваются или подавляются
(`| Out-Null`); диагностика — только `[Console]::Error.WriteLine()`.
Артефакт: **self-contained win-x64**, плоская структура ZIP (EXE в корне архива, без `publish/`),
обязательный состав фиксируется списком в спеке реализации. Другу не нужен .NET 8 Runtime, нужен
только КОМПАС. Цена — ~70 МБ на ассет.
Мелкая задача в сервер: флаг `--version`, печатающий версию в stdout и выходящий, — даёт `doctor`
сверку установленного бинаря с локом.
## 8. Команда `/kompas:doctor`
Командный промт (`plugin/commands/doctor.md`) с конкретным действием на каждый отказ:
1. ожидаемая версия из `server.lock.json`;
2. маркер `install.json` в `%LOCALAPPDATA%\kompas-mcp\<version>`: совпадают ли `version`/`sha256`;
3. `kompas-mcp.exe --version` против лока (когда флаг появится);
4. переопределение `KOMPAS_MCP_EXE`, если задано;
5. запущен ли процесс КОМПАС;
6. отвечает ли инструмент `kompas_status`;
7. итоговый отчёт.
## 9. CI (Gitea Actions в `kompas3d-mcp`)
**`ci.yml`** — push/PR в `main`: `dotnet build -c Release` + `dotnet test --filter Category=Unit`;
дополнительно проверка согласованности `plugin/server.lock.json` и `plugin/.claude-plugin/plugin.json`
(версии совпадают, `sha256` непустой).
**Интеграционные тесты в CI не выполняются никогда** — им нужен запущенный КОМПАС с GUI и лицензией.
Это граница, а не задача на будущее.
**`release.yml`** — на тег строго по шаблону `^v\d+\.\d+\.\d+$`, с `concurrency`-группой на весь
workflow (параллельные релизы сериализуются):
1. `dotnet publish src/Kompas.Mcp.Host/Kompas.Mcp.Host.csproj -c Release -r win-x64
--self-contained true -p:EnableWindowsTargeting=true -o <dir>` → zip → sha256;
2. проверка состава архива и того, что EXE — PE x64;
3. **upsert** релиза и ассета через Gitea API (`curl` + `secrets.GITEA_TOKEN`): повторный запуск на
том же теге не должен падать на «уже существует»; при совпадении SHA256 загрузка пропускается;
4. скачать ассет по итоговому публичному URL и **повторно сверить SHA256** — только после этого
публиковать указатель;
5. отказ, если версия тега не больше версии в текущей `dist`;
6. сформировать коммит в ветку `dist` поверх отмеченного тегом коммита: `server.lock.json`
(version/sourceSha/url/sha256) и `version` в `plugin.json`.
Требования к раннеру (готовится в отдельной сессии «LXC для Gitea runner»): .NET 8 SDK, доступ к
nuget.org, `curl` и `git`, токен Gitea с правами на релизы и запись в репозиторий.
Cross-publish с Linux ожидаемо работает (`EnableWindowsTargeting` подтягивает Windows targeting/runtime
packs; interop подключены как обычные ссылки, `ole32`/`oleaut32` разрешаются только в рантайме), но
Microsoft рекомендует финальный релиз собирать на Windows, поэтому **перед объявлением релиза годным
выполняется ручной smoke-run на Windows** (см. §12). Первым прогоном отдельно проверить, что
`HintPath` с обратными слешами из `Directory.Build.props` разрешается на Unix.
## 10. Каталог и установка
В `home-repo-cc/.claude-plugin/marketplace.json` добавляется одна запись, один раз:
```json
{ "name": "kompas",
"source": { "source": "git-subdir",
"url": "https://git.shahovalov.ru/mikhail/kompas3d-mcp.git",
"path": "plugin", "ref": "dist" },
"description": "КОМПАС-3D через MCP: построение деталей, сборки, чертежи, STEP",
"category": "cad" }
```
Каталог приватный, плагин тянется из публичного репозитория — знакомому нужен доступ только к каталогу.
Установка: доступ в Gitea → `/plugin marketplace add …/home-repo-cc.git` →
`/plugin install kompas@home-repo-cc` → `/kompas:doctor`.
Обновление — **три шага, документируются явно**: `/plugin marketplace update home-repo-cc` →
`/plugin update kompas@home-repo-cc` → при необходимости `/reload-plugins`. Полагаться на фоновое
автообновление для приватного HTTPS-каталога нельзя (credential-helper в фоне отключён). Знакомым
рекомендуется **SSH-ремоут каталога** (фоновые pull'ы аутентифицируются ключом из `ssh-agent`); при
HTTPS — выставить `CLAUDE_CODE_PLUGIN_KEEP_MARKETPLACE_ON_FAILURE=1`, чтобы неудачный фоновый pull
не сносил клон.
Предпосылки в `plugin/README.md`: Windows x64; установленный и **запущенный** КОМПАС-3D (проверено на
v24 Home); работа только на машине с КОМПАС (сервер — COM-клиент, не автономный CAD-движок);
репозиторий/чекаут вне OneDrive-дерева.
Жизненный цикл кеша (документируется, чтобы не пугало): Claude Code хранит каждую версию плагина в
отдельном каталоге и держит осиротевшую примерно 14 дней; активная сессия продолжает работать со
старым `CLAUDE_PLUGIN_ROOT` до `/reload-plugins`, поэтому старая и новая сессии могут одновременно
поднять серверы разных версий из разных каталогов `%LOCALAPPDATA%\kompas-mcp\<version>`. Сам
`%LOCALAPPDATA%\kompas-mcp` **не очищается** ни при обновлении, ни при удалении плагина — чистка
вручную.
## 11. Задел под Codex и opencode
Ни Codex, ни opencode не знают ни `${CLAUDE_PLUGIN_ROOT}`, ни маркетплейсов: для них модель —
клон репозитория и абсолютный путь. Задел выражается не декларацией, а тем, что контент не копируется:
- `adapters/codex/config.snippet.toml` — `[mcp_servers.kompas]` через тот же лаунчер + README:
куда вставлять (`~/.codex/config.toml`), навыки берутся из `.agents/skills` (создаёт sync-скрипт);
- `adapters/opencode/opencode.json` — фрагмент local-MCP + README;
- лаунчер параметризуется только `server.lock.json`, навыки лежат в одном месте — новый харнесс стоит
README и сниппета, а не форка контента.
Граница явная: это сниппеты и инструкция, а не дистрибутивы.
## 12. Риски и открытые вопросы
- **Право на редистрибуцию interop-DLL АСКОН — блокирующий вопрос до первого релиза**, а не до
публичности: self-contained ассет уже раздаётся знакомым. Если право не подтверждается, interop в
ассет не включается, и лаунчер должен брать разрешённые сборки из локальной установки SDK — это
меняет модель дистрибуции, поэтому выясняется первым делом.
- **Приватный каталог** требует у знакомого аккаунта в Gitea и настроенных кредов; фоновое
автообновление по HTTPS ненадёжно (см. §10) — проверить на живом человеке.
- **Скачивание исполняемого файла** закрыто sha256-пином в репозитории и повторной сверкой после
публикации.
- **Сборка `net8.0-windows` на Linux-раннере** не проверена; страховка — ручной Windows smoke-run
перед объявлением релиза годным, при провале — Windows-раннер.
- **Расход диска**: каждая версия сервера ~70 МБ в `%LOCALAPPDATA%`, чистка вручную.
- Ревью Codex сняло пункт про «рекурсию CI»: `release.yml` висит только на теге, бот-коммит его не
запускает; на `[skip ci]` полагаться не нужно.
## 13. Критерии приёмки
1. `tools/sync-agent-assets.ps1` на чистом клоне: навыки видны и в Claude Code, и в `.agents/skills`,
файлы физически существуют в одном месте; при невозможности junction — внятная ошибка, не копия.
2. `git status` чист после sync: содержимое junction'ов не попадает в индекс.
3. Тег `v*` даёт релиз с ассетом и коммит в `dist` с согласованными `server.lock.json` / `plugin.json`;
повторный запуск на том же теге проходит без падения.
4. Холодная установка: `/plugin install kompas@home-repo-cc` → первый запуск качает бинарь →
`/kompas:doctor` зелёный.
5. Две параллельные холодные установки (две сессии одновременно) обе стартуют успешно, каталог
версии не повреждён.
6. Обрыв загрузки и порча `sha256` в локе приводят к отказу с внятным сообщением в stderr и
отсутствию целевого каталога; повторный запуск восстанавливается.
7. Ненулевой код выхода сервера доходит до Claude Code (сервер не выглядит «успешно завершившимся»).
8. Канал: лаунчер получает `initialize` на stdin и отдаёт корректный JSON-RPC-ответ; проверяются
Unicode-содержимое, крупный ответ, поведение при EOF/shutdown, отсутствие осиротевшего
`kompas-mcp.exe`; в stdout нет посторонних строк.
9. Обновление во время активной старой сессии: старая сессия продолжает работать, новая поднимает
новую версию.
10. Windows smoke-run бинаря, собранного на Linux-раннере, перед объявлением релиза годным.
11. По playbook'у навыка `kompas:kompas-3d` строится деталь на свежей установке — навык не
ссылается на отсутствующие в плагине ресурсы.
## 14. Что из ревью отклонено
- **Защита от двух параллельных тегов через CAS/rebase-push и строгую монотонность semver в общем
виде.** У репозитория один мейнтейнер; достаточно `concurrency`-группы, шаблона тега и отказа при
неувеличивающейся версии. Полная машинерия — цена без сценария.
- **Обязательный Windows smoke-run как автоматический гейт релиза.** Windows-раннера нет и он не
планируется; заменён ручным пунктом приёмки (§13.10).
@@ -1,425 +0,0 @@
# Спек: вынос базы справки SDK в отдельный репозиторий и RAG
Дата: 2026-07-31
Статус: реализовано (см. §13 «Что получилось на самом деле»)
## 1. Цель
Убрать MD-базу справки КОМПАС SDK (`docs/Kompas3D_SDK/`, 2464 статьи, 10.4 МБ текста)
из репозитория `kompas3d-mcp` в отдельный приватный репозиторий и сделать её
**третьей базой знаний в RAG на [[CT 127 — rag-node]]**. Субагент
`kompas-sdk-research` перестаёт грепать файлы и ходит в MCP-эндпоинт RAG.
Побочная, но равноценная цель — **воспроизводимое обновление**: при выходе новой
версии КОМПАС справка пересобирается из
`C:\Program Files\ASCON\KOMPAS-3D vNN Home\SDK\Help\KOMPAS_SDK_ru-RU.zip`
одной командой, коммитится, а переиндексация происходит сама через Gitea Actions.
## 2. Исходные факты (проверены 2026-07-31)
### База и генератор
- `docs/Kompas3D_SDK/`: **2464** `*.md` (interfaces 778, enums 386, structures 231,
guides 1070) + `resources/` (25 изображений) + `index.md` (297 КБ, оглавление).
Текст без `index.md`**10.4 МБ**, средняя статья 4.3 КБ, крупнейшие статьи —
`interfaces/геометрия.md` (261 КБ), `ksdocument2d.md` (241 КБ).
- Заголовков `## ` (методы/свойства/секции) — **11 235**. Это нижняя оценка числа
чанков в RAG.
- Каждая статья уже несёт YAML-фронтматтер `title`, `type`, `api`, `domain`, `tags`,
`sources` — ровно те поля, по которым фильтрует `search_knowledge`.
- Генератор `tools/build_kompas3d_sdk.py` (757 строк, чистый stdlib) **удалён** из
рабочего дерева коммитом `e1337e4`, но восстановим: `git show 3452e21:tools/build_kompas3d_sdk.py`.
- **Пробел в воспроизводимости:** генератор читает `docs/kompas_sdk_index.tsv`
(строки `файл \t заголовок \t описание`), а сам его **не создаёт**; в git этот TSV
никогда не хранился. Без него пересборка невозможна.
- Пробел закрывается: в zip есть `js/hmcontent.js` — полное дерево TOC Help&Manual
в виде `hmLoadTOC({items:[{tp:"topiclink",lv:1,cp:"<заголовок>",hf:"<файл>.html",items:[…]}]})`.
Из него TSV строится однозначно (`cp` → title, `hf` → file, вложенность → раздел).
- Zip справки v24: **391 МБ**, 52 662 записи (26 270 `.html`, 26 301 `.js`, 57 изображений).
Распакованное зеркало ~1.4 ГБ, в `.gitignore` (`docs/KOMPAS_SDK_ru-RU/`).
- В TOC есть готовые статьи «Новые интерфейсы/свойства/методы в API КОМПАС-3D v24»
(`new_intrfs_v24.html`, `new_properties_v24.html`, `new_methods_v24.html`) — после
обновления на v25 аналогичные страницы попадут в базу и дадут ответ на вопрос
«что нового в v25» прямо из RAG.
### CT 127 (rag-node), состояние на 2026-07-31
- Все сервисы `active`: `qdrant`, `rag-embed`, `rag-rerank`, `rag-mcp` (8090),
`rag-mcp-work` (8091), `act-runner`. RAM 14 ГБ (used 7, free 6), rootfs 7.3/24 ГБ.
- Один код обслуживает несколько баз; инстансы различает только `EnvironmentFile`
юнита: `MCP_PORT`, `MCP_NAME`, `RAG_STATUS_PATH`, `QDRANT_COLLECTION`, `VAULT_DIR`,
`MCP_BEARER_TOKEN`, `RAG_CACHE_PREFIX`.
- Отбор в CI делает **строгий** `rag_select.py` из чекаута репозитория: включение
**только по тегу `rag/include`** во фронтматтере, режим «areas» явно запрещён
(`ValueError: bootstrap areas are forbidden; curated RAG is tag-only`).
- Чанкинг: по заголовкам `#..###` с уважением code-fence, затем по абзацам,
`max_tok=1000` + overlap; каждому чанку приписывается хлебная крошка
`«<title> — <heading path>»` — для справки это даёт в тексте чанка имя интерфейса
и имя метода, что прямо работает на ретрив.
- Payload чанка: `path`, `title`, `heading_path`, `chunk_index`, `chunk_text`, `tags`,
`type`, `checksum`, `git_commit`. Индексация **инкрементальная** по `checksum`
документа — при обновлении версии переэмбеддятся только изменившиеся статьи.
- Применение — `safe_rag_sync.py --source <checkout> --preflight` затем `--apply`:
candidate-коллекция из snapshot текущей, валидация (включая `degenerate_vectors`),
атомарное переключение alias, откат при ошибке.
- MCP отдаёт 5 инструментов: `search_knowledge` (фильтры `tags`, `type`,
`path_prefix`, реранк), `grep_knowledge` (ripgrep **только по путям из индекса**),
`get_document`, `get_chunk_context`, `knowledge_status`.
- Раннер `act_runner` — host executor, **python-only**: `uses:` JS-action'ов
(`actions/checkout@v4`) падает с `Cannot find: node in PATH`. Чекаут делает
`clone-vault.sh "$GITHUB_SHA"` (токен из `/etc/rag/gitea.env`). Раннер
зарегистрирован на instance-level и виден любому репозиторию Gitea.
- Лок `/run/lock/vault-lock` (`/run/lock/vault-rag-sync.lock`) — **общий на все базы**:
`llama-server` эмбеддингов один, индексатор глобально переключает его LoRA на
`passage`, параллельные синхронизации перепутали бы векторы.
### Смежное
- Спек упаковки в плагин (`2026-07-31-kompas-plugin-distribution-design.md`, §61) уже
постановил, что `docs/Kompas3D_SDK/` и субагент `kompas-sdk-research` **в плагин не
входят**, а `tools/verify-plugin.ps1` содержит `docs/Kompas3D_SDK` в списке
запрещённых путей. Вынос базы этому не противоречит — только упрощает проверку.
## 3. Принятые решения
| № | Решение | Почему |
|---|---|---|
| Р1 | Отдельный **приватный** репозиторий Gitea `mikhail/kompas-sdk-docs` | Справка © ООО «АСКОН», публиковать нельзя. Приватность — как у `rag-node`. |
| Р2 | Локально в `kompas3d-mcp` не остаётся ничего: `docs/Kompas3D_SDK/` удаляется, единственный путь к справке — MCP RAG | Решение пользователя. История git сохраняет базу (`git show e1337e4^:…`) — аварийный доступ есть без отдельного клона. |
| Р3 | В RAG одновременно **одна** версия SDK; предыдущие живут git-тегами `sdk-v24`, `sdk-v25` | Blue/green alias и так даёт откат; удвоение индекса ради «что было в v24» не окупается — сравнение версий делается `git diff` в репо доков. |
| Р4 | Включение в RAG — **по тегу `rag/include`**, который генератор проставляет каждой статье | Строгий CI-селектор поддерживает только тег-режим; правок общего кода не требуется. Тег во фронтматтере ещё и самодокументирует «эта статья идёт в индекс». |
| Р5 | Скрипты синка (`rag_select.py`, `safe_rag_sync.py`, минимальный валидатор) лежат **в самом репо доков**, как у рабочего vault | Сохраняет уже принятую модель trust-boundary: индексируется ровно запушенный чекаут своими скриптами. |
| Р6 | Третий инстанс: коллекция/alias `kompas_sdk`, MCP `rag-mcp-sdk.service` на порту **8092**, конфиг `/etc/rag/sdk.env`, чекаут `/opt/rag-sdk/vault`, свой bearer | Порты 8090/8091 заняты; изоляция баз токеном — уже действующая граница на этом узле. |
| Р7 | `index.md` (оглавление, 297 КБ) **не индексируется** | Семантический поиск и есть замена оглавлению; 297 КБ ссылок дали бы десятки мусорных чанков. Тега `rag/include` в нём не будет. |
| Р8 | Генератор и весь пайплайн обновления переезжают в репо доков (`tools/`) | В `kompas3d-mcp` им больше нечего делать: они не про MCP-сервер. |
## 4. Раскладка репозитория `kompas-sdk-docs`
```
README.md — что это, откуда, © АСКОН, как обновлять
VERSION — версия SDK и дата выгрузки: "24" / "2025-05-28"
sdk/
index.md — оглавление (в RAG не идёт, Р7)
interfaces/*.md 778
enums/*.md 386
structures/*.md 231
guides/*.md 1070
resources/* 25 изображений
tools/
build_index_tsv.py НОВЫЙ: js/hmcontent.js → kompas_sdk_index.tsv
build_kompas3d_sdk.py восстановлен из 3452e21, пути через argparse
update_sdk_docs.ps1 оркестратор: zip → распаковка → tsv → MD → отчёт diff
rag/
rag_select.py копия строгого селектора (hard-deny пуст)
safe_rag_sync.py копия blue/green-синка
validate_sdk_docs.py минимальный валидатор (см. §7)
deploy/
sdk.env.example шаблон /etc/rag/sdk.env
rag-mcp-sdk.service юнит третьего MCP
clone-vault-sdk.sh обёртка над общим clone-скриптом
.gitea/workflows/rag-sync.yml
.gitignore — .work/, *.zip, распакованное зеркало
```
Фронтматтер статьи после доработки генератора (добавляется единственное значение
`rag/include` в конец `tags`, остальное как сейчас):
```yaml
---
title: "3D-кривые и элементы тела"
type: interface
api: [api7]
domain: [3d]
tags: [interface, api7, 3d, bc2014000, rag/include]
sdk_version: "24"
sources: [bc2014000.html, imodelobjects.html]
---
```
`sdk_version` кладётся для трассируемости в payload-независимом виде (в чанк он не
попадает, но виден в `get_document`); фильтрации по нему не будет (Р3).
## 5. Пайплайн обновления при выходе новой версии SDK
Запускается **локально на Windows-машине** — только там есть установленный КОМПАС и
zip справки. rag-node к SDK доступа не имеет и не должен иметь.
```bash
pwsh tools/update_sdk_docs.ps1 -SdkVersion 25
```
Шаги оркестратора:
1. Находит zip: `C:\Program Files\ASCON\KOMPAS-3D v<N> *\SDK\Help\KOMPAS_SDK_ru-RU.zip`
(путь переопределяется `-ZipPath`). Падает, если версия в `VERSION` уже равна `<N>`
и не передан `-Force`.
2. Распаковывает в `.work/KOMPAS_SDK_ru-RU/``.gitignore`, ~1.4 ГБ).
3. `build_index_tsv.py` — парсит `js/hmcontent.js`, пишет `.work/kompas_sdk_index.tsv`.
Печатает число страниц; при расхождении с прошлым прогоном больше чем на 25 % —
предупреждение (формат TOC мог поменяться).
4. `build_kompas3d_sdk.py --help-dir .work/KOMPAS_SDK_ru-RU --index .work/kompas_sdk_index.tsv --out sdk/`
— та же логика, что дала текущую базу (KsAPI пропускается, TOC-страницы
пропускаются, изображения в `resources/`), плюс `rag/include` и `sdk_version` во
фронтматтере.
5. Печатает отчёт: статей записано / пропущено / ошибок / изображений и `git diff --stat`
в разрезе «добавлено / изменено / удалено статей».
6. Обновляет `VERSION` и счётчики в `README.md`.
Дальше вручную (сознательно — это точка контроля):
7. Просмотреть diff, особенно **удалённые** статьи: массовое исчезновение = сломался
парсер, а не АСКОН выкинул половину API.
8. `git commit``git tag sdk-v25``git push --follow-tags`.
9. Push в `main` запускает Gitea Actions (§7). Индексация инкрементальная: статьи с
неизменившимся `checksum` не переэмбеддиваются, поэтому переход v24→v25 стоит
минут, а не часа (в отличие от первой заливки).
10. Проверить приёмочными запросами (§11) и `knowledge_status`.
**Регрессионный прогон перед миграцией (обязателен).** До первого коммита новой базы
прогнать пайплайн на **v24** и сравнить результат с нынешним `docs/Kompas3D_SDK/`:
расхождение допустимо только во фронтматтере (`rag/include`, `sdk_version`). Это и
есть доказательство, что восстановленный TSV-шаг эквивалентен утраченному.
## 6. RAG-инстанс на CT 127
Новое (всё — по образцу рабочей базы, кода общего пайплайна не касается):
| Что | Значение |
|---|---|
| Репозиторий-источник | `mikhail/kompas-sdk-docs` |
| Чекаут | `/opt/rag-sdk/vault` |
| Alias / физическая коллекция | `kompas_sdk` / `kompas_sdk_v24_v1` → далее candidate'ы |
| Конфиг | `/etc/rag/sdk.env` |
| Статус | `/opt/rag-sdk/status.json` |
| MCP | `rag-mcp-sdk.service`, порт **8092**, `MCP_NAME=kompas-sdk` |
| Redis-префиксы | `rag:emb:k:` / `rag:rr:k:` (личная `p`, рабочая `w`) |
| Bearer | свой, генерируется при развёртывании |
| Traefik | опционально `kompas-sdk.svc.lan` → 8092 (как и у рабочей базы, можно позже) |
`VAULT_AREAS` пустой (тег-режим, Р4). `EMBED_URL`/`RERANK_URL`/`QDRANT_URL` — общие.
Оценки:
- Чанков: **1114 тыс.** (11 235 секций `##`, крупные секции дробятся по 1000 токенов).
Для сравнения: личная база 2410, рабочая 567.
- Векторы в Qdrant: 12 000 × 1024 × 4 Б ≈ **50 МБ** + payload с `chunk_text` ≈ 15 МБ.
Диска (15 ГБ свободно) хватает с запасом.
- Первая индексация при 5.7 emb/s ≈ **3550 минут**, и всё это время общий лок
держит синхронизацию личной и рабочей баз. Запускать вручную (`workflow_dispatch`)
в спокойное окно, а не «заодно с пушем».
## 7. CI (Gitea Actions в `kompas-sdk-docs`)
`.gitea/workflows/rag-sync.yml`, `runs-on: rag-node`, `concurrency: kompas-sdk-rag-sync`,
триггеры `push` в `main` по путям `sdk/**/*.md`, `tools/rag/**`,
`.gitea/workflows/rag-sync.yml` + `workflow_dispatch`.
Шаги — зеркало действующего workflow, с поправкой на конфиг:
1. `/opt/rag-sdk/clone-vault.sh "$GITHUB_SHA"` — чекаут ровно запушенного коммита
(никаких `uses:`, node в контейнере нет).
2. `validate_sdk_docs.py --root $VAULT_CHECKOUT` — минимальный валидатор:
- у каждой `sdk/**/*.md` парсится фронтматтер и есть `title`, `type`, `tags`;
- `rag/include` стоит у всех статей кроме `sdk/index.md`;
- число статей не упало более чем на 10 % против `validation-baseline.json`
(защита от «парсер сломался — половина базы исчезла»);
- в workflow нет `https?://` и присутствует маркер `clone-vault.sh "$GITHUB_SHA"`
(правило `rag.trust_boundary`, как в личном vault).
3. `safe_rag_sync.py --source $VAULT_CHECKOUT --preflight`, затем `--apply`
(`. /etc/rag/sdk.env`, `VAULT_AREAS=""`).
Раннер регистрировать заново не нужно — он instance-level.
## 8. Клиенты
### Субагент `kompas-sdk-research`
Инструменты меняются с `Glob, Grep, Read` на
`mcp__kompas-sdk__search_knowledge, mcp__kompas-sdk__grep_knowledge,
mcp__kompas-sdk__get_document, mcp__kompas-sdk__get_chunk_context,
mcp__kompas-sdk__knowledge_status`. Модель остаётся Haiku.
Порядок работы в инструкции (важен — он же снимает главный риск качества):
1. **Точное имя** (метод, интерфейс, enum, константа) → `grep_knowledge` первым:
`SetSideParam`, `ksHoleTypeEnum`, `o3d_incline`. Регекс работает, имена методов —
это заголовки `## SetSideParam - …`.
2. **Задачный вопрос** («чем построить массив по сетке») → `search_knowledge` с
фильтрами: `type` (`interface`/`enum`/`struct`/`guide`), `tags` (`api7`, `api5`,
`3d`, `2d`), `path_prefix` (`sdk/enums/`).
3. **Читать статью целиком**`get_chunk_context(document_id, chunk_index, window=2)`.
`get_document` **только** для мелких статей: `interfaces/геометрия.md` — 261 КБ,
выгрузка целиком сожжёт контекст субагента и обесценит делегирование.
4. Возврат — сжатая выжимка с `path` и `heading_path` как ссылкой на источник.
5. Явно сообщать «справка недоступна», если MCP не отвечает, — не выдумывать
сигнатуры (локального фоллбэка больше нет, Р2).
### Подключение MCP
В `kompas3d-mcp/.mcp.json` добавляется сервер `kompas-sdk` (`type: http`,
`url: http://192.168.1.152:8092/mcp`, `Authorization: Bearer ${KOMPAS_SDK_RAG_TOKEN}`).
**Токен в репозиторий не коммитится** — только через переменную окружения; сам
`.mcp.json` уже упоминается в `.gitignore` как машинно-специфичный, это надо
перепроверить при реализации.
## 9. Изменения в `kompas3d-mcp`
- Удалить `docs/Kompas3D_SDK/` (2464 файла). Историю **не** переписывать: pack репо
всего 4.75 МБ, MD жмётся отлично, `filter-repo` не окупается и ломает клоны.
- `CLAUDE.md` — секция «Navigating the SDK docs» переписывается на MCP RAG:
инструменты, порядок (grep → search → chunk-context), адрес и то, что база больше
не в репо.
- `.claude/agents/kompas-sdk-research.md` и `.codex/agents/kompas-sdk-research.toml`
новый набор инструментов и инструкция из §8.
- `README.md`, `docs/ARCHITECTURE.md` — упоминания базы как локальной папки.
- `.gitignore` — снять правило, разблокировавшее `docs/Kompas3D_SDK/`, и оставить
игнор зеркала `docs/KOMPAS_SDK_ru-RU/` на случай локальной выгрузки.
- `docs/superpowers/plans/2026-07-31-kompas-plugin-distribution.md` — в списке
`forbidden` путей `verify-plugin.ps1` строка `docs/Kompas3D_SDK` становится
бессмысленной (папки нет); либо оставить как страховку, либо убрать — решить при
реализации того плана.
- Заметку Obsidian `CT 127 — rag-node.md` дополнить третьей базой (таблица сервисов,
раздел «Клиенты», ToDo).
## 10. Риски
| Риск | Оценка | Что делаем |
|---|---|---|
| Восстановленный TSV-шаг даёт не ту базу, что была | средний | Регрессионный прогон на v24 с побайтовым сравнением (§5). Пока он не сошёлся — миграцию не начинаем. |
| Семантика плывёт на однотипной справке (11 тыс. почти одинаковых «Синтаксис Automation») | средний | `grep_knowledge` первым для точных имён; фильтры `type`/`tags`; реранкер. Приёмка на 10 реальных вопросах (§11). |
| Первая индексация 35–50 мин держит общий лок | средний | Запуск вручную через `workflow_dispatch` в спокойное окно; предупредить, что личная/рабочая синхронизация в это время ждёт. |
| OOM при индексации (прецедент 2026-07-25: `llama-server` anon-rss 6.5 ГБ) | низкий | `BATCH_TOKEN_BUDGET` уже снижен до 1500, `degenerate_vectors` в валидации кандидата; free 6 ГБ. Следить `free -g` в первый прогон. |
| CT 127 недоступен → справка недоступна | принятый | Сознательное решение Р2. Аварийный путь — `git show` в истории `kompas3d-mcp` или клон репо доков. |
| Bearer-токен утёк в git | низкий | Только `${KOMPAS_SDK_RAG_TOKEN}`; проверка на этапе приёмки. |
| Формат WebHelp изменится в v25 (`hmcontent.js`, разметка страниц) | средний | Отчёт §5.3 и валидатор §7.2 ловят обвал числа статей; чинится точечно в парсере. |
| Правовое: справка © АСКОН | — | Репозиторий приватный, наружу не публикуется; в `README.md` явная пометка о происхождении и лицензии. |
## 11. Критерии приёмки
1. Регрессия v24: пайплайн из чистого zip даёт `sdk/`, совпадающую с нынешней
`docs/Kompas3D_SDK/` с точностью до добавленных полей фронтматтера.
2. `knowledge_status` третьей базы показывает ≥ 2400 документов и 11–14 тыс. точек,
нулевых векторов нет.
3. `grep_knowledge("SetSideParam")` → попадание в статью с сигнатурой; то же для
`o3d_incline`, `ksHoleTypeEnum`, `CalcMassInertiaProperties`.
4. `search_knowledge` даёт релевантный топ-1 на 10 контрольных вопросах, взятых из
реальных задач проекта (массив по сетке, оболочка, переменные детали, привязка
размера к окружности, единицы МЦХ, направление уклона, …).
5. Фильтры работают: `type="enum"` возвращает только `sdk/enums/**`,
`tags=["api5"]` — только API5.
6. Изоляция: личный и рабочий bearer'ы на 8092 дают 401, sdk-токен на 8090/8091 — 401.
7. Субагент `kompas-sdk-research` отвечает на типовой запрос без Grep/Read и без
локальной копии базы; ответ содержит `path` статьи.
8. `docs/Kompas3D_SDK/` удалена, `dotnet build` и тесты не затронуты (документация
в сборке не участвует).
9. Повторный push в репо доков без изменений `sdk/**` не запускает переиндексацию;
push с правкой одной статьи переиндексирует только её (видно в логе `MOD`).
## 12. Этапы работ
1. **Восстановление пайплайна** (локально, ничего не ломает): достать генератор из
`3452e21`, написать `build_index_tsv.py`, параметризовать пути, прогнать на v24,
сравнить с текущей базой. Выход — доказанная воспроизводимость.
2. **Репозиторий**: создать приватный `kompas-sdk-docs` в Gitea, залить `sdk/`,
`tools/`, `deploy/`, `README`, `VERSION`, теги `sdk-v24`.
3. **Инстанс на CT 127**: `/etc/rag/sdk.env`, `/opt/rag-sdk/`, `clone-vault.sh`,
`rag-mcp-sdk.service` (8092), первая индексация вручную, проверка §11.2–11.6.
4. **CI**: workflow + `validate_sdk_docs.py` + baseline; проверить инкрементальность
(§11.9).
5. **Клиенты**: `.mcp.json`, субагент (Claude + Codex), проверка §11.7.
6. **Чистка `kompas3d-mcp`**: удаление базы, правки CLAUDE.md / README /
ARCHITECTURE, `.gitignore`; заметка Obsidian по CT 127.
7. **Прогон обновления вхолостую**: убедиться, что `update_sdk_docs.ps1 -Force` на том
же v24 даёт пустой diff — значит на v25 отработает предсказуемо.
## 13. Что получилось на самом деле
Реализовано 2026-07-31. Отличия от проекта выше — по фактам, а не по замыслу.
### Пробела в воспроизводимости не было
Последняя версия генератора (коммит `c761cba`, 939 строк — а не `3452e21`, который
я нашёл первым) читает реестр страниц **`zoom_pageinfo.js` прямо из справки** и
промежуточный `kompas_sdk_index.tsv` ей не нужен. Скрипт `build_index_tsv.py` не
понадобился; §2 и §5.3 описывают проблему, которой не существует.
Регрессия сошлась полностью: пересборка v24 из чистого zip дала **2466 файлов**,
побайтово совпадающих с прежней `docs/Kompas3D_SDK/`, кроме двух ожидаемых мест —
шапки `index.md` (я её переписал) и статьи `aux`. Второе оказалось находкой:
`AUX` — имя DOS-устройства, файл `aux.md` ломает checkout на Windows. В прежней
базе он звался `aux_.md` (кто-то переименовал вручную), теперь `_safe_stem()`
делает это детерминированно для `CON`/`PRN`/`AUX`/`NUL`/`COM1-9`/`LPT1-9`.
Уточнённые числа: **2465 статей** + `index.md`, **13 755 чанков**.
### Отбор — тегом, без правок общего кода
Как и в Р4: генератор проставляет `rag/include` и `sdk_version` во фронтматтер
каждой статьи. `sdk/index.md` фронтматтера не имеет вовсе и в индекс не идёт.
Селектор `tools/rag/rag_select.py` написан свой (самодостаточный, без `common.py`
личного vault), но алгоритмы нарезки скопированы 1:1 — чанки этой базы бьются
так же, как в остальных двух.
### Скорость индексации — в 7 раз хуже оценки
Оценка §6 «35–50 минут» строилась на бенчмарке с короткими заметками vault
(5.7 emb/s). На справке чанки в 5–10 раз длиннее: `llama-server` обрабатывает
батч в 3511 реальных токенов за ~12 с, то есть ~290 ток/с против ~2000 ток/с на
коротких текстах. Пауз между задачами в журнале нет — iGPU загружен полностью,
параллелизмом слотов это не лечится. Фактическая скорость первой заливки —
**30–55 чанков/мин, полный прогон ~5–7 часов**.
Практический вывод для регламента: **первую заливку и переезд на новую версию SDK
запускать ночью вручную** (`workflow_dispatch`), а не пушем «между делом» — общий
лок `/run/lock/vault-rag-sync.lock` блокирующий, поэтому синхронизация личной и
рабочей баз всё это время стоит в очереди (не падает, но и не идёт).
Инкрементальность спасает только последующие обновления: правка одной статьи
переиндексирует одну статью.
### Развёрнутая конфигурация
| Что | Значение |
|---|---|
| Репозиторий | `mikhail/kompas-sdk-docs` (private, issues/PR/wiki off, Actions on), тег `sdk-v24` |
| Коллекция / alias | `kompas_sdk_v24_v1` → alias `kompas_sdk` (payload-индексы как у остальных баз) |
| MCP | `rag-mcp-sdk.service`, порт 8092, `MCP_NAME=kompas-sdk`, свой bearer |
| Конфиг / чекаут / статус | `/etc/rag/sdk.env`, `/opt/rag-sdk/vault`, `/opt/rag-sdk/status.json` |
| Redis-префикс | `rag:emb:k:` |
Проверено: MCP отвечает `initialize` (protocol 2025-06-18), без токена — 401,
личный и рабочий токены на 8092 — 401, sdk-токен на 8090/8091 — 401.
### Побочная находка: реранк падал на длинных чанках
`search_knowledge` с `type=enum` возвращал `400 Bad Request` от реранкера вместо
результатов. Причина не в фильтре: слот реранкера — **1024 токена**
(`n_slots=4`, `n_ctx_slot=1024`; больше и не имеет смысла — jina-reranker-v2
обучен на `n_ctx_train=1024`), а `RERANK_DOC_CHARS` резал документы по **2000
символов**, что на кириллице и таблицах enum даёт до 1018 токенов. Один такой
документ ломал весь запрос. Личную и рабочую базы это не задевало только потому,
что их чанки короче.
Починено в репозитории `rag-node` (коммит `322b717`): обрезка снижена до 1400
символов (худшее наблюдаемое отношение — 0.45 токена на символ, то есть ~630
токенов), а сам реранк переведён в **fail-soft** — при отказе отдаётся порядок
векторного поиска, как и при недоступном Redis, а не ошибка инструмента.
### Приёмка
- `knowledge_status`: 2465 документов, 13 755 точек, коммит совпадает с `main`.
- `grep_knowledge`: попадания по `SetSideParam`, `CalcMassInertiaProperties`,
`ksHoleTypeEnum`, `GetGabarit`, `FindObjectsByPoint`.
- `search_knowledge`: 7 из 8 контрольных вопросов дают точный топ-1
(`ksShellDefinition`, `ksRibDefinition`, `ksRDimSourceParam`,
`ksLinearPatternBuildingTypeEnum`, «Переменные», «Операции», «Компоненты»).
Слабое место — «сопряжения компонентов сборки»: топ-1 уходит в
`ksUnionComponentsDefinition` (булево объединение), а не в `IMateConstraints3D`;
для таких запросов агенту надёжнее `grep_knowledge` по имени интерфейса.
- Фильтры `type`, `tags`, `path_prefix` возвращают только свои разделы.
- CI (`run 2`) прошёл целиком: дождался общего лока, отработал инкрементально
(`unchanged 2465, chunks 0`) и переключил alias — инкрементальность и триггеры
проверены живьём, без искусственного прогона.
- Регламент обновления прогнан вхолостую: повторная генерация v24 даёт пустой diff.
Набор приёмочных запросов лежит в репозитории доков —
`tools/rag/acceptance-queries.md`.