12 KiB
Дизайн: пакет 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.
- linear_pattern: цилиндр R5×H10 в центре (V₁≈785 мм³) →
linear_pattern([cyl], X, 3, 30)→ 3 непересекающихся цилиндра → V≈3·785=2356 (InRange ±5%); габарит X≈70 (от −5 до 65). - 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). - 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. - 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 оставляет оригинал) — проверяются
этими тестами; при расхождении корректируем по факту, сохранив суть проверки.