Merge feat/offset-loft: смещённая плоскость + операция по сечениям (loft)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ The base is committed to the repo (canonical) — no regeneration step. It cover
|
||||
|
||||
**v1 + v2 + STEP/assembly + direct B-rep edit + structural model inspection implemented and working** (full sketch→feature→inspect→STEP round-trip + move_face + describe_model validated end-to-end). Stack:
|
||||
**.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**.
|
||||
**56 MCP tools, 87 tests green (53 unit + 34 integration).** v2 added: sketch-on-face (by point and by index), revolve (boss/cut + sketch axis), fillet/chamfer (edge-by-point and by index), and Query tools `get_part_info` (МЦХ), `get_bounding_box`, `list_faces`, `list_edges`. After v2: `import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (assembly traversal via `TopPart → IParts7`). `move_face` (`FaceEditService`, `EditTools`) — direct B-rep face editing. After move_face: **structural model inspection** — `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` (`ModelInspectionService`, `InspectionTools`). `describe_model` is the preferred first call over `model_snapshot` (no token cost for image context; gives feature tree, bodies, variables, topology summary, МЦХ). Package A «richer sketches»: 7 new sketch primitives — `sketch_add_arc`, `sketch_add_arc_3points`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`. `PartModeler` refactored into partial classes: `PartModeler.cs` (core), `PartModeler.Sketch.cs` (2D primitives), `PartModeler.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` class in `Core/Modeling`. Package B «forming ops»: **`shell`** (оболочка, `o3d_shellOperation=43`) + **`rib`** (ребро жёсткости, `o3d_ribOperation=44`) + **`sweep`** (кинематическая операция, `o3d_baseEvolution=45`). Additional Edit tools: **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`**.
|
||||
**58 MCP tools, 89 tests green (53 unit + 36 integration).** v2 added: sketch-on-face (by point and by index), revolve (boss/cut + sketch axis), fillet/chamfer (edge-by-point and by index), and Query tools `get_part_info` (МЦХ), `get_bounding_box`, `list_faces`, `list_edges`. After v2: `import_step`, `export_step` (`ConversionService`, `ConversionTools`), `list_components` (assembly traversal via `TopPart → IParts7`). `move_face` (`FaceEditService`, `EditTools`) — direct B-rep face editing. After move_face: **structural model inspection** — `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` (`ModelInspectionService`, `InspectionTools`). `describe_model` is the preferred first call over `model_snapshot` (no token cost for image context; gives feature tree, bodies, variables, topology summary, МЦХ). Package A «richer sketches»: 7 new sketch primitives — `sketch_add_arc`, `sketch_add_arc_3points`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`. `PartModeler` refactored into partial classes: `PartModeler.cs` (core), `PartModeler.Sketch.cs` (2D primitives), `PartModeler.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` class in `Core/Modeling`. Package B «forming ops» complete: **`shell`** (оболочка, `o3d_shellOperation=43`) + **`rib`** (ребро жёсткости, `o3d_ribOperation=44`) + **`sweep`** (кинематическая операция, `o3d_baseEvolution=45`) + **`loft`** (по сечениям, `o3d_baseLoft=30`). Auxiliary geometry (start of package E): **`sketch_create_on_offset_plane`** (смещённая плоскость, `o3d_planeOffset=14`) — unlocked `loft`. Additional Edit tools: **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`**.
|
||||
See [`README.md`](README.md) and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); design decisions/caveats in
|
||||
[`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md).
|
||||
|
||||
@@ -52,14 +52,16 @@ Key implementation facts (don't relearn):
|
||||
- Logs go to **stderr** (stdout is the MCP channel). Integration tests reuse one КОМПАС via `KompasFixture`; artifacts land in gitignored `.scratch/`.
|
||||
- **Structural model inspection** (`ModelInspectionService`, `InspectionTools`): `describe_model` — one-call structural "passport" (bounding box + МЦХ + bodies + topology summary + feature tree + variables); preferred over snapshot. `list_features` — feature tree with params (depth/radius/legs). `list_bodies` — list solid/surface bodies with face count. `list_variables` — model variables (name/expression/value, external/info flags). `describe_face` — drill-down by index (type, area, normal, radius, edge count). `describe_edge` — drill-down by index (type, length, adjacent faces, vertices). `measure` — distance/angle between two objects (face|edge|vertex by index, unit `ST_MIX_MM`).
|
||||
- **Feature tree API** (don't relearn): read via API5 `ksPart.GetFeature()` → `(ksFeature).SubFeatureCollection(true,false)` → `ksFeatureCollection`. Cast `(ksFeature)part` does NOT work (RCW QI returns null) — must use `GetFeature()`. `ksFeature.type` returns only coarse `o3d_entity` and does NOT distinguish operations; precise type and params come from `ksFeature.GetObject()` → `ksEntity.GetDefinition()` and matching definition type (`ksBossExtrusionDefinition.GetSideParam`, `ksFilletDefinition.radius`, `ksChamferDefinition.GetChamferParam`, …). Node name ("Элемент выдавливания:1", "Скругление:1") is localized by КОМПАС itself.
|
||||
- **STEP import/export, `list_components`, `move_face`, `split_solid_by_plane`, `move_body`, `boolean_union`, full inspection layer, richer sketch primitives (package A), `shell`, `rib`, and `sweep` (package B) are done.** Not yet done: remaining package B forming ops — `draft`, `hole`; `loft` deferred (requires offset plane — future package E); patterns/arrays (package C), parametrics (package D), 2D drawing, assembly building. Known caveat: boss/cut direction on a selected face depends on face-normal orientation (`forward` may need flipping — agent picks via snapshot or `describe_face` normal). See `docs/OPEN_QUESTIONS.md` → «Ревью v2».
|
||||
- **STEP import/export, `list_components`, `move_face`, `split_solid_by_plane`, `move_body`, `boolean_union`, full inspection layer, richer sketch primitives (package A), `shell`, `rib`, `sweep`, `loft` (package B complete), `sketch_create_on_offset_plane` (package E start) are done.** Not yet done: `draft` (no API5, requires API7), `hole`; patterns/arrays (package C), parametrics (package D), 2D drawing, assembly building. Known caveat: boss/cut direction on a selected face depends on face-normal orientation (`forward` may need flipping — agent picks via snapshot or `describe_face` normal). See `docs/OPEN_QUESTIONS.md` → «Ревью v2».
|
||||
- **"STEP import without history"** detected structurally: `bodyCount > 0 && no formative features && features.Count <= bodyCount+1` (only origin + body). STEP with edits (move_face: "Смещённая плоскость", "Разрезать", "Переместить грани", "Булева операция") — that already has history.
|
||||
- **Topology/measure API**: `ksFaceDefinition.EdgeCollection` / `GetCylinderParam` / `GetSurface().GetNormal` + `normalOrientation`; `ksEdgeDefinition.GetAdjacentFace(bool)` / `GetVertex(bool)` → `ksVertexDefinition.GetPoint`; bodies via `ksPart.BodyCollection()` → `ksBody` (`IsSolid` / `FaceCollection`); variables via `ksPart.VariableCollection()` → `ksVariable`; measurements via `ksPart.GetMeasurer()` → `ksMeasurer` (`SetObject1/2`, `unit=ST_MIX_MM`, `Calc`, `distance`/`MinDistance`/`angle` in degrees / `IsAngleValid`).
|
||||
- **STEP import COM pattern** (verified): `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (pass format code, not DLL path) → `IConverter.ConverterParameters(cmd)` → set `IAdditionConvertParameters.Format=ksConverterFromSTEP` → `IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`. Export: `ConvertToAdditionFormat` with format codes for AP203/AP214/AP242. Param co-classes (AdditionConvertParameters etc.) are NOT CoCreatable — only via converter factories. Assembly traversal: `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`. Extract component: `prm.NeedCreateComponentsFiles=true` (writes .m3d files next to STEP) + `IPart7.OpenSourceDocument`.
|
||||
- **Direct B-rep face editing (`move_face`)** (verified on top_spacer, 39.45→41.45 mm height): API7 face object retrieved via `IPart7.FindObjectsByPoint(x,y,z,true)` → cast to `KompasAPI7.IFace`; containers acquired at runtime via COM-QI: `(ISurfaceContainer)part`, `(IModelContainer)part`; `FaceMover` (SetFaces + Offset + Direction + Update) moves the face. distance>0 = outward (add material), <0 = inward. Works on both parametric and imported B-rep geometry. Full split-reposition workflow (SplitSolids/BodyRepositions) is driveable but not yet exposed as tools.
|
||||
- **Shell operation (`shell`, package B)**: `ksShellDefinition` via `ksPart.NewEntity(o3d_shellOperation=43)`; `thickness` (mm) + `thinType` (bool: `true`=inward/`false`=outward; mapping: `thinType = !outward`) + `FaceArray()` (open faces, ≥1, by index from `list_faces`). Helper `SelectFaceByIndex` in `PartModeler.cs` (analogous to `SelectEdgeByIndex`, with range check). Validation: `thickness>0`, dedup via `Distinct`, range check before mutation. Transient RCWs in `ShellAsync`/`SelectFaceByIndex` are intentionally not released (consistent with Fillet/Chamfer pattern).
|
||||
- **Rib operation (`rib`, package B)**: `ksRibDefinition` via `ksPart.NewEntity(o3d_ribOperation=44)`. Params: `sketchId`, `thickness` (mm), `side` (`left|right|up|down` → 0/1/2/3 via `SketchGeometry.RibSide()`), `symmetric` (bool), `angle` (°). `SetSketch(sketch)` → set `index=0`, `angle`, `side`; `SetThinParam(dtBoth, t/2, t/2)` when `symmetric=true`, else `SetThinParam(dtNormal, t, 0)`. Empirical: the rib contour must float in the gap (not touch the body at endpoints) — КОМПАС extends the web to the body automatically.
|
||||
- **Sweep operation (`sweep`, package B)**: `ksBaseEvolutionDefinition` via `ksPart.NewEntity(o3d_baseEvolution=45)`. Params: `profileSketchId` (closed profile sketch), `pathSketchId` (open/closed path sketch on a different plane, typically perpendicular). `SetSketch(profile)` → `PathPartArray().Add(path)` → `sketchShiftType=0` → `SetThinParam(false)` → `Create()`. Validation: `profileSketchId != pathSketchId`. Profile and path must be on different (usually perpendicular) planes. **Loft deferred** — requires offset plane (future package E «auxiliary geometry»).
|
||||
- **Sweep operation (`sweep`, package B)**: `ksBaseEvolutionDefinition` via `ksPart.NewEntity(o3d_baseEvolution=45)`. Params: `profileSketchId` (closed profile sketch), `pathSketchId` (open/closed path sketch on a different plane, typically perpendicular). `SetSketch(profile)` → `PathPartArray().Add(path)` → `sketchShiftType=0` → `SetThinParam(false)` → `Create()`. Validation: `profileSketchId != pathSketchId`. Profile and path must be on different (usually perpendicular) planes.
|
||||
- **Offset plane (`sketch_create_on_offset_plane`, package E start)**: `OpenSketchOnOffsetPlaneAsync` in `PartModeler.Sketch.cs`. `ksPart.NewEntity(o3d_planeOffset=14)` → `ksPlaneOffsetDefinition` (`SetPlane(basePlane)` + `offset` mm + `direction`) → `Create()` → sketch on that plane. Used to place sketches at arbitrary heights — enables `loft` with parallel sections. `entity.Create()` result is checked (reliability fix applied to all `CreateSketchOn*` paths).
|
||||
- **Loft operation (`loft`, package B)**: `ksBaseLoftDefinition` via `ksPart.NewEntity(o3d_baseLoft=30)`. Params: `sketchIds[]` (≥2 closed profile sketches on different parallel planes). `Sketchs().Add(each sketch)` → `SetLoftParam(closed=false, false, true)` → `SetThinParam(false)` → `Create()`. Requires parallel section sketches at different heights — use `sketch_create_on_offset_plane` for the non-base sections.
|
||||
- **Richer sketch primitives (package A, ksDocument2D API5 wrappers)**: `ksArcBy3Points` (3-point arc), `ksArcByAngle` (centre/radius/start-end angles in degrees, counterClockwise flag), `ksEllipse` via `ksEllipseParam` — struct obtained via `KompasObject.GetParamStruct(ko_EllipseParam=22)`; **property names are `A`/`B` (uppercase) in the interop**; `ksRegularPolygon` via `ksRegularPolygonParam` (`ko_RegularPolygonParam=92`) — `describe = !inscribed`; NURBS spline via `ksNurbs(order=4)` + `ksNurbsPoint` loop + `ksEndObj`; `ksPoint`. Param structs are released after use. `PartModeler` split into partial classes: `PartModeler.cs` (registry/helpers/NewParam<T>/reset), `PartModeler.Sketch.cs` (all 2D primitives), `PartModeler.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` class (`Core/Modeling`) holds enum mappings (`ArcDirection`, `PolygonDescribe`) and validators (`RequirePositive`, `RequireVertexCount`, `RequirePoints`). Polyline/spline point lists use `record SketchPoint(X, Y)` with JSON names `x`/`y`.
|
||||
|
||||
## КОМПАС-3D API architecture (the critical context)
|
||||
|
||||
@@ -39,14 +39,14 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
}
|
||||
```
|
||||
|
||||
## Инструменты (56 инструментов)
|
||||
## Инструменты (58 инструментов)
|
||||
|
||||
| Группа | Инструменты |
|
||||
|---|---|
|
||||
| System | `kompas_connect`, `kompas_status`, `kompas_set_visible` |
|
||||
| Documents | `document_create`, `document_open`, `document_save`, `document_save_as`, `document_close`, `document_active` |
|
||||
| Sketch | `sketch_create`, `sketch_create_on_face`, `sketch_create_on_face_index`, `sketch_add_line`, `sketch_add_circle`, `sketch_add_rectangle`, `sketch_add_axis`, `sketch_add_arc`, `sketch_add_arc_3points`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`, `sketch_close` |
|
||||
| Features | `extrude_boss`, `extrude_cut`, `revolve_boss`, `revolve_cut`, `fillet_edge`, `chamfer_edge`, `fillet_edge_index`, `chamfer_edge_index`, `shell`, `rib`, `sweep`, `rebuild` |
|
||||
| Sketch | `sketch_create`, `sketch_create_on_face`, `sketch_create_on_face_index`, `sketch_create_on_offset_plane`, `sketch_add_line`, `sketch_add_circle`, `sketch_add_rectangle`, `sketch_add_axis`, `sketch_add_arc`, `sketch_add_arc_3points`, `sketch_add_ellipse`, `sketch_add_polyline`, `sketch_add_polygon`, `sketch_add_spline`, `sketch_add_point`, `sketch_close` |
|
||||
| Features | `extrude_boss`, `extrude_cut`, `revolve_boss`, `revolve_cut`, `fillet_edge`, `chamfer_edge`, `fillet_edge_index`, `chamfer_edge_index`, `shell`, `rib`, `sweep`, `loft`, `rebuild` |
|
||||
| Edit | `move_face`, `split_solid_by_plane`, `move_body`, `boolean_union` |
|
||||
| Inspection | `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` |
|
||||
| Vision | `model_snapshot` (PNG-снимок; fallback — сначала `describe_model`) |
|
||||
@@ -70,7 +70,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe
|
||||
```
|
||||
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции, конвертация, снимок, инспекция модели
|
||||
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов
|
||||
tests/Kompas.Mcp.Tests/ 87 тестов: unit + integration (integration требуют КОМПАС)
|
||||
tests/Kompas.Mcp.Tests/ 89 тестов: unit + integration (integration требуют КОМПАС)
|
||||
libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common)
|
||||
docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/)
|
||||
usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант)
|
||||
|
||||
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep»). Актуализировано по коду.
|
||||
> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-E «offset plane»). Актуализировано по коду.
|
||||
> Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md).
|
||||
> Имена интерфейсов и перечислений сверены со справкой SDK по MD-базе знаний `docs/Kompas3D_SDK/` (навык `kompas-sdk-research`).
|
||||
|
||||
@@ -113,7 +113,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|---|---|
|
||||
| **Kompas.Mcp.Host** | Точка входа. Сборка MCP-сервера (stdio), DI (в т.ч. `ConversionService`), логи в stderr, старт и владение STA-потоком, graceful shutdown. Определения инструментов по категориям (System / Documents / Sketch / Features / Query / Conversion). **Тонкий слой**: валидация аргументов → постановка задачи на STA-поток → форматирование результата/ошибки в MCP-ответ. |
|
||||
| **Kompas.Mcp.Core** | COM-слой. Менеджер соединения, менеджер документов, построитель эскизов и операций, `ConversionService` (STEP импорт/экспорт), `QueryService` (МЦХ, грани, рёбра, компоненты), `ModelInspectionService` (дерево операций, тела, переменные, drill-down, измерения), снимок. Все паттерны API5/API7 живут здесь. |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 87 тестов (53 unit + 34 integration). |
|
||||
| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 89 тестов (53 unit + 36 integration). |
|
||||
|
||||
Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики.
|
||||
|
||||
@@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
---
|
||||
|
||||
## 5. Карта инструментов (56 инструментов)
|
||||
## 5. Карта инструментов (58 инструментов)
|
||||
|
||||
Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`.
|
||||
|
||||
@@ -136,6 +136,7 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
|
||||
**Sketch** (2D-геометрия внутри 3D-эскиза)
|
||||
- `sketch_create` — на базовой плоскости (`o3d_planeXOY` / `XOZ` / `YOZ`) или на выбранной грани.
|
||||
- `sketch_create_on_offset_plane` — эскиз на плоскости, смещённой от базовой (XOY/XOZ/YOZ) на `offset` мм; вспомогательная геометрия через `o3d_planeOffset=14` / `ksPlaneOffsetDefinition`. Разблокирует `loft` (параллельные сечения на разной высоте). Начало пакета E «вспомогательная геометрия».
|
||||
- `sketch_add_line` / `sketch_add_circle` / `sketch_add_arc` / `sketch_add_arc_3points` / `sketch_add_rectangle`.
|
||||
- `sketch_add_ellipse` (centre, semi-axes A/B, rotation angle) / `sketch_add_polyline` (point chain, closed) / `sketch_add_polygon` (regular, inscribed/circumscribed) / `sketch_add_spline` (cubic NURBS, closed) / `sketch_add_point`.
|
||||
- `sketch_close` — завершить редактирование.
|
||||
@@ -148,7 +149,8 @@ MCP client ──stdio──> Host (поток-пул, async)
|
||||
- `revolve_boss` / `revolve_cut` — угол.
|
||||
- `shell` — превратить тело в оболочку: открыть грани по индексам (`faceIndices`, ≥1, из `list_faces`), задать толщину стенки (`thickness` мм), направление (`outward`, по умолчанию внутрь). Реализовано через API5 `ksPart.NewEntity(o3d_shellOperation=43)` → `ksShellDefinition` → `FaceArray()` → `Create()`. Маппинг: `thinType = !outward`.
|
||||
- `rib` — построить ребро жёсткости от разомкнутого контура эскиза до тела. Параметры: `sketchId`, `thickness` (мм), `side` (`left|right|up|down` → 0/1/2/3), `symmetric` (толщина симметрична плоскости), `angle` (уклон стенок, °). Реализовано через `ksPart.NewEntity(o3d_ribOperation=44)` → `ksRibDefinition` (`SetSketch`, `SetThinParam`). `symmetric=true`: `SetThinParam(dtBoth, t/2, t/2)`; иначе `SetThinParam(dtNormal, t, 0)`. Контур должен не касаться тела концами — КОМПАС дотягивает полотно сам.
|
||||
- `sweep` — кинематическая операция: перемещение замкнутого профиля (эскиз `profileSketchId`) вдоль траектории (эскиз `pathSketchId`), образуя сплошное тело. Профиль и траектория на разных (обычно перпендикулярных) плоскостях. Реализовано через `ksPart.NewEntity(o3d_baseEvolution=45)` → `ksBaseEvolutionDefinition` (`SetSketch(профиль)` → `PathPartArray().Add(траектория)` → `sketchShiftType=0` → `SetThinParam(false)`) → `Create()`. Валидация: `profileSketchId != pathSketchId`. **Loft отложен** — требует смещённой плоскости (будущий пакет E).
|
||||
- `sweep` — кинематическая операция: перемещение замкнутого профиля (эскиз `profileSketchId`) вдоль траектории (эскиз `pathSketchId`), образуя сплошное тело. Профиль и траектория на разных (обычно перпендикулярных) плоскостях. Реализовано через `ksPart.NewEntity(o3d_baseEvolution=45)` → `ksBaseEvolutionDefinition` (`SetSketch(профиль)` → `PathPartArray().Add(траектория)` → `sketchShiftType=0` → `SetThinParam(false)`) → `Create()`. Валидация: `profileSketchId != pathSketchId`.
|
||||
- `loft` — операция по сечениям: построить сплошное тело по ≥2 закрытым эскизам-сечениям на параллельных плоскостях. Реализовано через `ksPart.NewEntity(o3d_baseLoft=30)` → `ksBaseLoftDefinition` (`Sketchs().Add(каждое сечение)` → `SetLoftParam(closed=false,false,true)` → `SetThinParam(false)`) → `Create()`. Для сечений не на базовых плоскостях использовать `sketch_create_on_offset_plane`.
|
||||
- `rebuild` — перестроить деталь.
|
||||
|
||||
**Edit** (прямое редактирование)
|
||||
@@ -314,12 +316,12 @@ dotnet build -c Release -r win-x64 # сборка
|
||||
|
||||
## 10. Дорожная карта
|
||||
|
||||
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели).
|
||||
**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-E start):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, снимок; `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`; `import_step`, `export_step`, `list_components`; **`move_face`**, **`split_solid_by_plane`**, **`move_body`**, **`boolean_union`** (прямое редактирование и булевы операции); **`describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure`** (структурный осмотр модели).
|
||||
|
||||
**Следующие приоритеты:**
|
||||
1. Пакет B «формообразующие» (продолжение) — `draft` (требует API7), `hole`. **`loft` отложен** — требует смещённой плоскости (пакет E «вспомогательная геометрия»).
|
||||
1. `draft` (уклон, требует API7), `hole`.
|
||||
2. Пакет C «массивы» — `ILinearPattern`, `ICircularPattern`.
|
||||
3. Пакет E «вспомогательная геометрия» — смещённые плоскости (offset plane); разблокирует `loft`.
|
||||
3. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу.
|
||||
4. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена.
|
||||
5. Свойства документа: `IPropertyMng` / `IPropertyKeeper`.
|
||||
6. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты.
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
<!-- 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 (56 инструментов)</span>
|
||||
<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+offset-plane (58 инструментов)</span>
|
||||
<h1>КОМПАС-3D <span class="g">MCP-сервер</span><br>управление CAD языком LLM</h1>
|
||||
<p class="lead">MCP-сервер, который превращает операции КОМПАС-3D — создание документов,
|
||||
эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом —
|
||||
@@ -261,7 +261,7 @@
|
||||
впереди.</p>
|
||||
|
||||
<div class="prog">
|
||||
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep (пакет B) · навык kompas-3d · 56 инструментов · 87 тестов</span></div>
|
||||
<div class="progrow"><span>Общий прогресс</span><span>v3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · offset-plane (пакет E start) · навык kompas-3d · 58 инструментов · 89 тестов</span></div>
|
||||
<div class="bar"><i style="width:95%"></i></div>
|
||||
</div>
|
||||
|
||||
@@ -297,18 +297,18 @@
|
||||
<!-- TOOLS -->
|
||||
<div class="panel">
|
||||
<h3>Инструменты v3+ <span class="tag done">ГОТОВО</span></h3>
|
||||
<p class="meta">56 инструментов, отдаются по MCP-протоколу</p>
|
||||
<p class="meta">58 инструментов, отдаются по 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 (точка/индекс) · 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> (кинематическая) · rebuild</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> (по сечениям) · 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>Conversion</code>: import_step · export_step</span></li>
|
||||
<li><span class="mark wip">→</span><span>Пакет B (продолжение: draft/hole) · loft отложен (нужна offset-плоскость) · пакет C (массивы)</span></li>
|
||||
<li><span class="mark wip">→</span><span>draft (API7) · hole · пакет C (массивы) · пакет E (продолжение)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -319,7 +319,7 @@
|
||||
<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">Итого 87 тестов зелёных (53 unit + 34 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Итого 89 тестов зелёных (53 unit + 36 integration)</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Сборка <code>kompas-mcp.exe</code>, README с конфигом клиента</span></li>
|
||||
<li class="muted"><span class="mark ok">✓</span><span class="t">Навык <code>kompas-3d</code> (методика) + полигон <code>usecases/</code></span></li>
|
||||
<li><span class="mark todo"></span><span>In-process тест MCP-клиента, релизный publish</span></li>
|
||||
@@ -341,8 +341,8 @@
|
||||
<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> ✓ (кинематическая) · далее: draft · hole · loft отложен (нужна offset-плоскость)</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе</div><h4>draft · hole · пакет C · пакет E</h4><p>draft (API7) · hole · пакет C (массивы: linear/circular) · пакет E (вспомогательная геометрия: offset-плоскости — разблокирует loft)</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); draft · hole — впереди</p></div>
|
||||
<div class="tl cur"><div class="ph">далее · в работе</div><h4>draft · hole · пакет C</h4><p>draft (API7) · hole · пакет C (массивы: linear/circular) · пакет E продолжение (ось, угловая плоскость)</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Дизайн: смещённая плоскость + операция по сечениям (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.
|
||||
@@ -299,6 +299,38 @@ public sealed partial class PartModeler
|
||||
return id;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Операция по сечениям (loft): построить сплошное тело по ≥2 закрытым эскизам-сечениям
|
||||
/// (в порядке списка). closed — замкнуть сечения в кольцо. Возвращает id операции.</summary>
|
||||
public Task<int> LoftAsync(IReadOnlyList<int> sketchIds, bool closed = false, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sketchIds);
|
||||
var ids = sketchIds.Distinct().ToList();
|
||||
if (ids.Count < 2)
|
||||
throw new ArgumentException("Нужно минимум 2 сечения для операции по сечениям.", nameof(sketchIds));
|
||||
var sketches = ids.Select(RequireSketch).ToList(); // проверяем все id до мутации
|
||||
foreach (var s in sketches) CloseSketchCore(s);
|
||||
|
||||
var part = GetTopPart();
|
||||
var entity = part.NewEntity((short)Obj3dType.o3d_baseLoft) as ksEntity
|
||||
?? throw new InvalidOperationException("NewEntity(o3d_baseLoft) вернул null.");
|
||||
var def = entity.GetDefinition() as ksBaseLoftDefinition
|
||||
?? throw new InvalidOperationException("GetDefinition() операции по сечениям вернул не ksBaseLoftDefinition.");
|
||||
var arr = def.Sketchs() as ksEntityCollection
|
||||
?? throw new InvalidOperationException("Sketchs() вернул не ksEntityCollection.");
|
||||
foreach (var s in sketches) arr.Add(s.Entity);
|
||||
def.SetLoftParam(closed, false, true); // closed, flipVertex=false, autoPath=true
|
||||
def.SetThinParam(false, 0, 0, 0); // сплошное тело
|
||||
|
||||
if (!entity.Create())
|
||||
throw new InvalidOperationException(
|
||||
"Create() операции по сечениям вернул FALSE (несовместимые сечения или неверный порядок?).");
|
||||
|
||||
var id = _nextId++;
|
||||
_features[id] = entity;
|
||||
return id;
|
||||
}, ct);
|
||||
|
||||
/// <summary>Перестроить документ.</summary>
|
||||
public Task RebuildAsync(CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
|
||||
@@ -60,6 +60,30 @@ public sealed partial class PartModeler
|
||||
return CreateSketchOn(part, face);
|
||||
}, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Создать смещённую плоскость (база + offset мм) и открыть на ней эскиз. Возвращает id эскиза.
|
||||
/// Снимает ограничение «только базовые плоскости»: эскиз на произвольной высоте/смещении.
|
||||
/// </summary>
|
||||
public Task<int> OpenSketchOnOffsetPlaneAsync(BasePlane basePlane, double offset, bool direction = true, CancellationToken ct = default)
|
||||
=> _dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (!double.IsFinite(offset))
|
||||
throw new ArgumentException("Смещение должно быть конечным числом.", nameof(offset));
|
||||
var part = GetTopPart();
|
||||
var planeEntity = part.NewEntity((short)Obj3dType.o3d_planeOffset) as ksEntity
|
||||
?? throw new InvalidOperationException("NewEntity(o3d_planeOffset) вернул null.");
|
||||
var pdef = planeEntity.GetDefinition() as ksPlaneOffsetDefinition
|
||||
?? throw new InvalidOperationException("GetDefinition() плоскости вернул не ksPlaneOffsetDefinition.");
|
||||
var baseEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(basePlane)) as ksEntity
|
||||
?? throw new InvalidOperationException("Не удалось получить базовую плоскость.");
|
||||
pdef.SetPlane(baseEntity);
|
||||
pdef.offset = offset;
|
||||
pdef.direction = direction;
|
||||
if (!planeEntity.Create())
|
||||
throw new InvalidOperationException("Create() смещённой плоскости вернул FALSE.");
|
||||
return CreateSketchOn(part, planeEntity);
|
||||
}, ct);
|
||||
|
||||
/// <summary>Общее ядро: создать эскиз на заданной плоскости/грани и открыть на редактирование.</summary>
|
||||
private int CreateSketchOn(ksPart part, ksEntity planeEntity)
|
||||
{
|
||||
@@ -69,7 +93,8 @@ public sealed partial class PartModeler
|
||||
?? throw new InvalidOperationException("GetDefinition() эскиза вернул не ksSketchDefinition.");
|
||||
|
||||
def.SetPlane(planeEntity);
|
||||
entity.Create();
|
||||
if (!entity.Create())
|
||||
throw new InvalidOperationException("Create() эскиза вернул FALSE.");
|
||||
|
||||
var editor = def.BeginEdit() as ksDocument2D
|
||||
?? throw new InvalidOperationException("BeginEdit() не вернул ksDocument2D.");
|
||||
|
||||
@@ -122,6 +122,18 @@ public sealed class FeatureTools(KompasSession session, PartModeler modeler)
|
||||
return $"Кинематическая операция создана (профиль {profileSketchId} по траектории {pathSketchId}), id={id}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "loft")]
|
||||
[Description("Операция по сечениям (loft): построить сплошное тело по нескольким закрытым эскизам-сечениям (sketchIds, минимум 2, в порядке списка). Сечения обычно на параллельных плоскостях — используйте sketch_create_on_offset_plane для сечений на разной высоте. closed=true — замкнуть сечения в кольцо. Возвращает id операции.")]
|
||||
public async Task<string> Loft(
|
||||
[Description("Id эскизов-сечений (минимум 2, в порядке построения)")] int[] sketchIds,
|
||||
[Description("Замкнуть сечения в кольцо")] bool closed = false)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var id = await modeler.LoftAsync(sketchIds, closed);
|
||||
var sections = sketchIds?.Distinct().Count() ?? 0;
|
||||
return $"Операция по сечениям создана ({sections} сечений), id={id}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "rebuild")]
|
||||
[Description("Перестроить активный документ.")]
|
||||
public async Task<string> Rebuild()
|
||||
|
||||
@@ -27,6 +27,18 @@ public sealed class SketchTools(KompasSession session, PartModeler modeler)
|
||||
return $"Эскиз создан на грани в точке ({x}, {y}, {z}), id={id}. Координаты геометрии — в локальной плоскости эскиза.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "sketch_create_on_offset_plane")]
|
||||
[Description("Создать эскиз на плоскости, смещённой от базовой (XOY|XOZ|YOZ) на offset мм, и открыть на редактирование. Позволяет строить геометрию на произвольной высоте/смещении (например сечения для операции loft). direction — направление смещения от базовой плоскости. Возвращает id эскиза.")]
|
||||
public async Task<string> CreateOnOffsetPlane(
|
||||
[Description("Базовая плоскость: XOY | XOZ | YOZ")] string basePlane,
|
||||
[Description("Смещение от базовой плоскости, мм")] double offset,
|
||||
[Description("Прямое направление смещения")] bool direction = true)
|
||||
{
|
||||
await session.ConnectAsync();
|
||||
var id = await modeler.OpenSketchOnOffsetPlaneAsync(BasePlanes.Parse(basePlane), offset, direction);
|
||||
return $"Эскиз создан на смещённой плоскости {basePlane}+{offset}, id={id}.";
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "sketch_add_circle")]
|
||||
[Description("Добавить окружность в открытый эскиз. Координаты — в плоскости эскиза, мм.")]
|
||||
public async Task<string> AddCircle(int sketchId, double centerX, double centerY, double radius)
|
||||
|
||||
@@ -114,4 +114,55 @@ public sealed class FeatureOpsTests
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OffsetPlane_sketch_extrudes_at_height()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Эскиз на XOY, смещённой на +20 по Z → цилиндр R5×H10 с основанием на Z≈20.
|
||||
var s = await _modeler.OpenSketchOnOffsetPlaneAsync(BasePlane.XOY, offset: 20);
|
||||
await _modeler.AddCircleAsync(s, 0, 0, 5);
|
||||
await _modeler.CloseSketchAsync(s);
|
||||
await _modeler.ExtrudeAsync(s, depth: 10);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var box = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(box.MinZ, 19.5, 20.5); // основание поднято на 20 мм
|
||||
var v = (await _query.GetPartInfoAsync()).Volume;
|
||||
Assert.InRange(v, Math.PI * 25 * 10 * 0.95, Math.PI * 25 * 10 * 1.05);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Loft_two_squares_makes_solid()
|
||||
{
|
||||
await _docs.CreateAsync(KompasDocumentType.Part);
|
||||
try
|
||||
{
|
||||
// Два квадратных сечения 20×20 на XOY (Z=0) и на XOY+40 → призма 20×20×40.
|
||||
var s1 = await _modeler.OpenSketchAsync(BasePlane.XOY);
|
||||
await _modeler.AddRectangleAsync(s1, -10, -10, 10, 10);
|
||||
await _modeler.CloseSketchAsync(s1);
|
||||
var s2 = await _modeler.OpenSketchOnOffsetPlaneAsync(BasePlane.XOY, offset: 40);
|
||||
await _modeler.AddRectangleAsync(s2, -10, -10, 10, 10);
|
||||
await _modeler.CloseSketchAsync(s2);
|
||||
|
||||
var id = await _modeler.LoftAsync(new[] { s1, s2 });
|
||||
Assert.True(id > 0);
|
||||
await _modeler.RebuildAsync();
|
||||
|
||||
var v = (await _query.GetPartInfoAsync()).Volume; // 20×20×40 = 16000
|
||||
Assert.InRange(v, 16000 * 0.9, 16000 * 1.1);
|
||||
|
||||
// Габарит подтверждает соединение сечений по высоте: ≈20×20×40.
|
||||
var box = await _query.GetBoundingBoxAsync();
|
||||
Assert.InRange(box.SizeX, 20 * 0.95, 20 * 1.05);
|
||||
Assert.InRange(box.SizeY, 20 * 0.95, 20 * 1.05);
|
||||
Assert.InRange(box.SizeZ, 40 * 0.95, 40 * 1.05);
|
||||
}
|
||||
finally { await _docs.CloseAsync(save: false); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user