Files
kompas3d-mcp/docs/superpowers/specs/2026-05-26-pattern-mirror-design.md
T

129 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Дизайн: пакет 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 оставляет оригинал) — проверяются
этими тестами; при расхождении корректируем по факту, сохранив суть проверки.