diff --git a/CLAUDE.md b/CLAUDE.md index e58f687..a670d76 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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**. -**72 MCP tools, 156 tests green (98 unit + 58 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`**. Package C «patterns & mirror» complete: **`linear_pattern`** (линейный массив, `o3d_meshCopy=35`) + **`circular_pattern`** (круговой массив, `o3d_circularCopy=36`) + **`mirror_operation`** (зеркальная копия операций, `o3d_mirrorOperation=48`) + **`mirror_body`** (зеркало тела, `o3d_mirrorAllOperation=49`). **`hole`** (`HoleService`, `FeatureTools`) — цилиндрическое отверстие (сквозное или глухое) через **API7** (`IModelContainer`/`IHole3D`/`IHoleDisposal`); первая формообразующая операция на API7 (прецедент ранее только у `move_face`). **`hole_counterbore`** — отверстие с цековкой (`ksHTCounterbore`, `ISpotfacingHoleParameters`). **`hole_countersink`** — отверстие с зенковкой (`ksHTCountersinking`, `ICountersinkHoleParameters`). **`hole_conic`** — коническое отверстие (`ksHTConic`, `IConicHoleParameters`: `ConicType=ksCNAngle`, `ConicAngle`); завершает набор типов отверстий. `HoleCore` рефакторен: принимает `ksHoleTypeEnum holeType` + `Action configure` (настройка `HoleParameters` после установки `HoleType`, т.к. параметрический подынтерфейс недоступен до фиксации типа). Базовый `hole` = `ksHTBase` + `configure=null`. **`draft`** (уклон, `o3d_incline=42`) — наклон граней на угол относительно нейтральной координатной плоскости; реализован через **API5** `ksInclineDefinition` (операция называется Incline в API5, не Draft). Package D «parametrics» started: **`create_variable`**, **`set_variable`**, **`delete_variable`** (`VariableService`, `VariableTools`) — CRUD переменных модели через API5. `list_variables` исправлен (читает `ksPart.GetFeature().VariableCollection`, а не `ksPart.VariableCollection()`). **Ограничение:** переменная управляет геометрией только в параметрической модели (где размеры эскиза связаны с именем переменной); наши эскизы строятся литеральными координатами → `set_variable` хранит/вычисляет значение, но геометрию не меняет. Связь эскизных размеров с переменными (API2D) — не реализована. **Assembly class:** **`assembly_add_component`** (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`) — вставка компонента (.m3d/.a3d) из файла в активную сборку с заданием положения origin (мм, мировая СК сборки); возвращает индекс и имя вставленного компонента. **`assembly_add_mate`** — наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам; `mateType`: `coincidence` (грани заподлицо) | `distance` (зазор `value` мм, `value>0`); решатель позиционирует компоненты; возвращает статус (`valid`). Подтверждено вживую: `coincidence` (ширина сборки 120→40 мм), `distance(30)` (→70 мм). +**73 MCP tools, 177 tests green (116 unit + 61 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`**. Package C «patterns & mirror» complete: **`linear_pattern`** (линейный массив, `o3d_meshCopy=35`) + **`circular_pattern`** (круговой массив, `o3d_circularCopy=36`) + **`mirror_operation`** (зеркальная копия операций, `o3d_mirrorOperation=48`) + **`mirror_body`** (зеркало тела, `o3d_mirrorAllOperation=49`). **`hole`** (`HoleService`, `FeatureTools`) — цилиндрическое отверстие (сквозное или глухое) через **API7** (`IModelContainer`/`IHole3D`/`IHoleDisposal`); первая формообразующая операция на API7 (прецедент ранее только у `move_face`). **`hole_counterbore`** — отверстие с цековкой (`ksHTCounterbore`, `ISpotfacingHoleParameters`). **`hole_countersink`** — отверстие с зенковкой (`ksHTCountersinking`, `ICountersinkHoleParameters`). **`hole_conic`** — коническое отверстие (`ksHTConic`, `IConicHoleParameters`: `ConicType=ksCNAngle`, `ConicAngle`); завершает набор типов отверстий. `HoleCore` рефакторен: принимает `ksHoleTypeEnum holeType` + `Action configure` (настройка `HoleParameters` после установки `HoleType`, т.к. параметрический подынтерфейс недоступен до фиксации типа). Базовый `hole` = `ksHTBase` + `configure=null`. **`draft`** (уклон, `o3d_incline=42`) — наклон граней на угол относительно нейтральной координатной плоскости; реализован через **API5** `ksInclineDefinition` (операция называется Incline в API5, не Draft). Package D «parametrics» started: **`create_variable`**, **`set_variable`**, **`delete_variable`** (`VariableService`, `VariableTools`) — CRUD переменных модели через API5. `list_variables` исправлен (читает `ksPart.GetFeature().VariableCollection`, а не `ksPart.VariableCollection()`). **Ограничение:** переменная управляет геометрией только в параметрической модели (где размеры эскиза связаны с именем переменной); наши эскизы строятся литеральными координатами → `set_variable` хранит/вычисляет значение, но геометрию не меняет. Связь эскизных размеров с переменными (API2D) — не реализована. **Assembly class:** **`assembly_add_component`** (`AssemblyService`, `AssemblyTools`, namespace `Kompas.Mcp.Core.Assemblies`) — вставка компонента (.m3d/.a3d) из файла в активную сборку с заданием положения origin (мм, мировая СК сборки); возвращает индекс и имя вставленного компонента. **`assembly_add_mate`** — наложить сопряжение между гранями двух компонентов активной сборки, выбранными по мировым точкам; `mateType`: `coincidence` (грани заподлицо) | `distance` (зазор `value` мм, `value>0`); решатель позиционирует компоненты; возвращает статус (`valid`). Подтверждено вживую: `coincidence` (ширина сборки 120→40 мм), `distance(30)` (→70 мм). **2D-чертёж (начало вехи):** **`drawing_create_standard_views`** (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`) — создать стандартные ассоциативные виды (спереди/сверху/слева) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже; возвращает число созданных видов (3). Первый инструмент работы с чертежом. See [`README.md`](README.md) and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); design decisions/caveats in [`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). @@ -53,7 +53,7 @@ Key implementation facts (don't relearn): - **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); reads via `ksPart.GetFeature().VariableCollection` (all variables, including user-created) with fallback to `ksPart.VariableCollection()` (external-only — bug was fixed). `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`). - **Variable management (package D start, `VariableService`, `VariableTools`)**: `create_variable(name, value, note?, external?)` — creates a model variable; `set_variable(name, expression)` — sets expression (constant `"30"` or formula `"width*2+5"`); `delete_variable(name)` — deletes (fails if dependents exist). **Key API facts (don't relearn):** variable creation MUST use `ksPart.GetFeature().VariableCollection` (property on root ksFeature) — NOT `ksPart.VariableCollection()` (returns external-only). Expression is the leading field; value is derived. Changes applied via `ksPart.RebuildModel()` (return checked); after rebuild the RCW variable is stale → re-read via fresh collection (`ReadValueFresh`). Dependent variables recalculate automatically. Deletion fails if dependents exist (RemoveVariable returns FALSE). **Geometry limitation:** variables control geometry only in a parametric model where sketch dimensions are linked to variable names; our sketches are built with literal coordinates → `set_variable` stores/computes the value but does NOT change geometry. Linking sketch dimensions to variables (parametric sketches via API2D) is not yet implemented. - **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`, `sweep`, `loft` (package B complete), `sketch_create_on_offset_plane` (package E start), `linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body` (package C complete), `hole`, `hole_counterbore`, `hole_countersink`, `hole_conic` (all API7 via `HoleService`; `hole_conic` uses `ksHTConic`/`IConicHoleParameters`), `draft` (API5 `ksInclineDefinition`), `create_variable`/`set_variable`/`delete_variable` (package D start), `assembly_add_component`, `assembly_add_mate` (both API7, `AssemblyService`; mate types `coincidence`/`distance` verified) are done.** Not yet done: parametric sketches — **investigated, unavailable via COM API** (`ksCDimWithVariable` cannot be constructed from external automation; see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`); 2D drawing; additional mate types (parallel/perpendicular/concentric/angle/tangency); package E continuation (axis, angle plane). 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), `linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body` (package C complete), `hole`, `hole_counterbore`, `hole_countersink`, `hole_conic` (all API7 via `HoleService`; `hole_conic` uses `ksHTConic`/`IConicHoleParameters`), `draft` (API5 `ksInclineDefinition`), `create_variable`/`set_variable`/`delete_variable` (package D start), `assembly_add_component`, `assembly_add_mate` (both API7, `AssemblyService`; mate types `coincidence`/`distance` verified), `drawing_create_standard_views` (API7, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`) are done.** Not yet done: parametric sketches — **investigated, unavailable via COM API** (`ksCDimWithVariable` cannot be constructed from external automation; see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`); 2D-чертёж: оформление (размеры, рамка, штамп) — далее; additional mate types (parallel/perpendicular/concentric/angle/tangency); package E continuation (axis, angle plane). 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`); all variables via `ksPart.GetFeature().VariableCollection` → `ksVariable` (external-only: `ksPart.VariableCollection()` — do NOT use for reading all variables); 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`. @@ -67,6 +67,7 @@ Key implementation facts (don't relearn): - **Hole operations (`hole`, `hole_counterbore`, `hole_countersink`, `hole_conic`)**: all implemented via **API7** (not API5 — `ksHoleDefinition` absent in interop). Service `src/Kompas.Mcp.Core/Modeling/HoleService.cs`. `HoleCore` helper is parameterised with `ksHoleTypeEnum holeType` + `Action configure` callback (configure sets `HoleParameters` **after** `HoleType` is assigned, because the type-specific sub-interface — e.g. `ISpotfacingHoleParameters` — is not accessible until the type is fixed). Base `hole` = `ksHTBase` + `configure=null`. `hole_counterbore`: `ksHTCounterbore` + configure sets `(ISpotfacingHoleParameters)hole.HoleParameters` (`SpotfacingDiameter`, `SpotfacingDepth`); validates `spotDiameter>diameter` and `depth>spotDepth` for blind. `hole_countersink`: `ksHTCountersinking` + configure sets `(ICountersinkHoleParameters)hole.HoleParameters` (`CountersinkType=ksCTDiameterAngle`, `CountersinkDiameter`, `CountersinkAngle`); validates `sinkDiameter>diameter`, `00`). Scope: `coincidence`/`distance` verified live; `parallel`/`perpendicular`/`concentric`/`angle`/`tangency` — future increments. DI: `AddSingleton()` in `Program.cs`. +- **Drawing service (`drawing_create_standard_views`, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)**: entry via API7 `IKompasDocument2D` — requires an active drawing document (`DocumentType==ksDocumentDrawing`). Core call: `doc2d.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", projTypes, x, y, scale, dx=20, dy=20)` where `projTypes=object[]{1,3,5}` (ksPtFront/ksPtUp/ksPtLeft passed as SAFEARRAY VT_I4). Success check: `created==3` strictly; content check: sum of `IView.ObjectCount` for newly created views (identified by `IView.Number` delta) must be `>0`, otherwise error + rollback via `IView.Delete`. No rebuild needed — `ObjectCount>0` immediately on a new drawing. Validation: `DrawingValidation.NormalizeModelPath` (accepts .m3d/.a3d), `RequireScale>0`. Model file must be saved (`document_save_as`) before calling. Returns `DrawingViewsResult{Created,Total}`. DI: `AddSingleton()` in `Program.cs`. Confirmed live: `Views.Count` 1→4 (system view + 3 projections). - **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/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) diff --git a/README.md b/README.md index 20aa249..cac8c41 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe } ``` -## Инструменты (72 инструмента) +## Инструменты (73 инструмента) | Группа | Инструменты | |---|---| @@ -54,6 +54,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe | Variables | `create_variable`, `set_variable`, `delete_variable` | | Conversion | `import_step`, `export_step` | | Assembly | `assembly_add_component`, `assembly_add_mate` | +| Drawing | `drawing_create_standard_views` | | Validation | `validate_part` | ### Типовой сценарий @@ -72,7 +73,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/ 156 тестов: unit + integration (integration требуют КОМПАС) +tests/Kompas.Mcp.Tests/ 177 тестов: unit + integration (integration требуют КОМПАС) libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common) docs/ архитектура, план, презентация, MD-база знаний SDK (Kompas3D_SDK/) usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 72a4dbe..d0c877d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Архитектура MCP-сервера КОМПАС-3D (предлагаемый вариант) -> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-D start «переменные»+package-E «offset plane»+`hole`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`+`assembly_add_mate`). Актуализировано по коду. +> Статус: **реализовано и работает** (v1+v2+STEP/assembly+direct-edit+inspection+package-A «богатые эскизы»+package-B «shell+rib+sweep+loft»+package-C «массивы и зеркало»+package-D start «переменные»+package-E «offset plane»+`hole`+`hole_counterbore`+`hole_countersink`+`hole_conic`+`draft`+`assembly_add_component`+`assembly_add_mate`+`drawing_create_standard_views` (начало вехи «2D-чертёж»)). Актуализировано по коду. > Сопутствующий контекст по COM API КОМПАС — в [`../CLAUDE.md`](../CLAUDE.md). > Имена интерфейсов и перечислений сверены со справкой SDK по MD-базе знаний `docs/Kompas3D_SDK/` (навык `kompas-sdk-research`). @@ -112,8 +112,8 @@ 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, измерения), `AssemblyService` (вставка компонентов в сборку, namespace `Kompas.Mcp.Core.Assemblies`), снимок. Все паттерны API5/API7 живут здесь. | -| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 156 тестов (98 unit + 58 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | +| **Kompas.Mcp.Core** | COM-слой. Менеджер соединения, менеджер документов, построитель эскизов и операций, `ConversionService` (STEP импорт/экспорт), `QueryService` (МЦХ, грани, рёбра, компоненты), `ModelInspectionService` (дерево операций, тела, переменные, drill-down, измерения), `AssemblyService` (вставка компонентов в сборку, namespace `Kompas.Mcp.Core.Assemblies`), `DrawingService` (стандартные виды чертежа, namespace `Kompas.Mcp.Core.Drawings`), снимок. Все паттерны API5/API7 живут здесь. | +| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 177 тестов (116 unit + 61 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики. @@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async) --- -## 5. Карта инструментов (72 инструмента) +## 5. Карта инструментов (73 инструмента) Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`. @@ -208,6 +208,9 @@ MCP client ──stdio──> Host (поток-пул, async) - `assembly_add_component` — вставить компонент (.m3d деталь или .a3d подсборка) из файла в активную сборку; задать положение origin (x,y,z мм, мировая СК сборки). Возвращает индекс и имя вставленного компонента. Реализовано через API7: `IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)` → `IPart7` → `IPlacement3D.SetOrigin(x,y,z)` → `UpdatePlacement(true)` → `RebuildModel(true)`. `RequireActiveAssembly` проверяет `doc.DocumentType == ksDocumentAssembly` до обращения к `TopPart`. Откат при неуспехе через `IFeature7.Delete`. DI: `AddSingleton()` в `Program.cs`. - `assembly_add_mate` — наложить сопряжение между гранями двух компонентов активной сборки по мировым точкам. Параметры: `mateType` (`coincidence` — грани заподлицо | `distance` — зазор `value` мм), `x1,y1,z1`, `x2,y2,z2`, `value=0`. Решатель позиционирует компоненты; возвращает статус `valid`. Реализовано через API7: `top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)` → `BaseObject1/2` (грани через `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` → `Update()` → `RebuildModel(true)`; проверка `mate.Valid` (false → ошибка + откат `mate.Owner.Delete`). Enum `MateType { Coincidence, Distance }` в `src/Kompas.Mcp.Core/Assemblies/MateType.cs`; маппинг на `MateConstraintType` (`mc_Coincidence=0`, `mc_Distance=5`). Scope: `coincidence`/`distance` проверены вживую; прочие типы (`parallel`, `concentric`, …) — будущие инкременты. +**Drawing — 2D-чертёж (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`)** +- `drawing_create_standard_views(partFilePath, scale, x, y)` — создать стандартные ассоциативные виды (спереди/сверху/слева, `ksPtFront/ksPtUp/ksPtLeft`) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже. Реализовано через API7 `IKompasDocument2D.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", object[]{1,3,5}, x, y, scale, dx=20, dy=20)` — `object[]` передаётся как SAFEARRAY VT_I4. Проверки: `created==3` строго; непустота — сумма `IView.ObjectCount` новых видов (по разнице `IView.Number`) `>0`, иначе ошибка + откат (`IView.Delete`). Возвращает `DrawingViewsResult{Created,Total}`. Модель должна быть сохранена до вызова. + **Vision — визуальная обратная связь (fallback)** - `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**, чтобы мультимодальный агент *увидел* промежуточный результат. Использовать для визуально-пространственных @@ -341,14 +344,14 @@ dotnet build -c Release -r win-x64 # сборка ## 10. Дорожная карта -**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component+assembly_add_mate):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `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`** (структурный осмотр модели); **`create_variable`, `set_variable`, `delete_variable`** (управление переменными, package D start); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`); **`assembly_add_mate`** (сопряжения `coincidence`/`distance`, API7, `IMateConstraints3D`). +**Реализовано (v1+v2+STEP/assembly+direct-edit+inspection+package-A+package-B+package-C+package-D start+package-E start+hole+hole_counterbore+hole_countersink+hole_conic+draft+assembly_add_component+assembly_add_mate+drawing_create_standard_views):** документы, эскизы (полный набор 2D-примитивов: линия, окружность, дуга, дуга по 3 точкам, прямоугольник, эллипс, ломаная, правильный многоугольник, сплайн NURBS, точка), **вспомогательная геометрия (`sketch_create_on_offset_plane`)**, выдавливание/вырез, вращение, скругление/фаска, **оболочка (`shell`)**, **ребро жёсткости (`rib`)**, **кинематическая операция (`sweep`)**, **операция по сечениям (`loft`)**, **линейный/круговой массив и зеркало (`linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`)**, **отверстие (`hole`, API7)**, **цековка (`hole_counterbore`, API7)**, **зенковка (`hole_countersink`, API7)**, **коническое отверстие (`hole_conic`, API7)**, **уклон (`draft`, API5 `ksInclineDefinition`)**, снимок; `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`** (структурный осмотр модели); **`create_variable`, `set_variable`, `delete_variable`** (управление переменными, package D start); **`assembly_add_component`** (вставка компонента в сборку, API7, `AssemblyService`); **`assembly_add_mate`** (сопряжения `coincidence`/`distance`, API7, `IMateConstraints3D`); **`drawing_create_standard_views`** (стандартные ассоциативные виды чертежа, API7, `DrawingService`). **Следующие приоритеты:** 1. Пакет D «параметрика» (продолжение) — связь размеров эскизов с переменными (параметрические эскизы через API2D). ⚠️ **Исследовано**: `ksCDimWithVariable` недоступен из внешней автоматизации — см. `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`. 2. Пакет E «вспомогательная геометрия» (продолжение) — ось, точка, плоскость по трём точкам / по углу. 3. Рассечение/перемещение тела как MCP-инструменты (`SplitSolids`/`BodyRepositions`) — механика есть, продуктизация не закончена. 4. Свойства документа: `IPropertyMng` / `IPropertyKeeper`. -5. Полноценное 2D-черчение: виды, линии/дуги/окружности, размеры (`ILinearDimension`, …), штриховки, тексты. +5. **2D-чертёж (продолжение):** оформление — размеры (`ILinearDimension`, …), рамка, штамп, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`). 6. Сборки — продолжение: дополнительные типы сопряжений (`parallel`, `perpendicular`, `concentric`, `angle`, `tangency`; `coincidence`/`distance` реализованы); авто-позиционирование компонентов; спецификации (`ISpecification`). 7. Транспорт **HTTP/SSE** — как опция для удалённых клиентов. diff --git a/docs/OPEN_QUESTIONS.md b/docs/OPEN_QUESTIONS.md index c050650..5999fdb 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -188,3 +188,6 @@ stateful-сессии? Пока полагаемся на последовате ### ✅ Сопряжения в сборках — реализованы (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 не нужен). Итог: **73 инструмента, 177 тестов (116 unit + 61 integration).** Следующий шаг: оформление чертежа (размеры, рамка, штамп). diff --git a/docs/presentation.html b/docs/presentation.html index b4814cc..2b503b7 100644 --- a/docs/presentation.html +++ b/docs/presentation.html @@ -128,7 +128,7 @@
- Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate (72 инструмента) + Статус: 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 (73 инструмента)

КОМПАС-3D MCP-сервер
управление CAD языком LLM

MCP-сервер, который превращает операции КОМПАС-3D — создание документов, эскизы, 3D-операции, параметры — в инструменты для языковой модели. Под капотом — @@ -261,7 +261,7 @@ впереди.

-
Общий прогресс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 · навык kompas-3d · 72 инструмента · 156 тестов
+
Общий прогресс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 · навык kompas-3d · 73 инструмента · 177 тестов
@@ -297,7 +297,7 @@

Инструменты v3+ ГОТОВО

-

72 инструмента, отдаются по MCP-протоколу

+

73 инструмента, отдаются по MCP-протоколу

  • System: connect · status · set_visible
  • Documents: create · open · save · save_as · close · active
  • @@ -310,7 +310,8 @@
  • Variables: create_variable · set_variable · delete_variable (пакет D)
  • Conversion: import_step · export_step
  • Assembly: assembly_add_component (вставка компонента .m3d/.a3d в сборку, API7) · assembly_add_mate (сопряжения coincidence/distance, API7)
  • -
  • пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
  • +
  • Drawing: drawing_create_standard_views (стандартные ассоциативные виды чертежа, API7)
  • +
  • 2D-чертёж: оформление (размеры, рамка, штамп) · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
@@ -321,7 +322,7 @@
  • Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText
  • Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция
  • -
  • Итого 156 тестов зелёных (98 unit + 58 integration)
  • +
  • Итого 177 тестов зелёных (116 unit + 61 integration)
  • Сборка kompas-mcp.exe, README с конфигом клиента
  • Навык kompas-3d (методика) + полигон usecases/
  • In-process тест MCP-клиента, релизный publish
  • @@ -350,7 +351,8 @@
    отверстия под крепёж · готово ✓

    Цековка, зенковка и конус

    hole_counterbore (ksHTCounterbore) · hole_countersink (ksHTCountersinking) · hole_conic (ksHTConic, IConicHoleParameters) · HoleCore рефакторен (configure-колбэк) · 70 инструментов · 119 тестов

    сборки start · готово ✓

    Вставка компонентов

    assembly_add_component (API7 IParts7.AddFromFile + IPlacement3D)

    сборки инкремент 2 · готово ✓

    Сопряжения

    assembly_add_mate (coincidence/distance, API7 IMateConstraints3D) · IntegrationTestBase · 72 инструмента · 156 тестов

    -
    далее · в работе

    пакет E продолжение

    вспомогательная геометрия (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API

    +
    2D-чертёж · начато ▶

    Стандартные виды

    drawing_create_standard_views (AddStandartViews, API7) · 73 инструмента · 177 тестов · далее: оформление (размеры, рамка, штамп)

    +
    далее · в очереди

    пакет E + прочее

    вспомогательная геометрия (ось, угловая плоскость) · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency) · пакет D параметрика — параметрические эскизы недоступны через COM API

diff --git a/docs/superpowers/specs/2026-05-27-drawing-standard-views-design.md b/docs/superpowers/specs/2026-05-27-drawing-standard-views-design.md new file mode 100644 index 0000000..1d011c8 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-drawing-standard-views-design.md @@ -0,0 +1,128 @@ +# Дизайн: стандартные виды чертежа (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()` в `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 = промотированный спайк; спайк-файл удаляется.) + +## Дальнейшее (вне спека) +- Выбор набора видов (+изометрия, +сечения), позиция/масштаб листа, размеры/обозначения на видах, + рамка/основная надпись. diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs new file mode 100644 index 0000000..ea94d5a --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -0,0 +1,112 @@ +using System.Runtime.Versioning; +using Kompas.Mcp.Core.Threading; +using Kompas6Constants; +using KompasAPI7; + +namespace Kompas.Mcp.Core.Drawings; + +/// +/// Операции с 2D-чертежами через API7 (IViews). Все вызовы — на STA-потоке +/// . +/// +[SupportedOSPlatform("windows")] +public sealed class DrawingService +{ + private readonly KompasSession _session; + private readonly KompasDispatcher _dispatcher; + + public DrawingService(KompasSession session, KompasDispatcher dispatcher) + { + _session = session ?? throw new ArgumentNullException(nameof(session)); + _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); + } + + /// + /// Создать на активном чертеже стандартные ассоциативные виды (спереди/сверху/слева) сохранённой + /// 3D-модели (.m3d/.a3d). Главный вид привязывается к точке + /// (,) листа, мм; масштаб — . + /// + public Task CreateStandardViewsAsync(string partFilePath, double scale, + double x, double y, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => CreateStandardViewsCore(partFilePath, scale, x, y), ct); + + // Стандартная тройка проекций: ksPtFront=1, ksPtUp=3, ksPtLeft=5 (ksRelativeProjectionTypeEnum). + private static readonly object[] StandardProjections = { 1, 3, 5 }; + private const int ExpectedCreated = 3; + private const double GapMm = 20; // зазоры между видами (DX=DY) + + private DrawingViewsResult CreateStandardViewsCore(string partFilePath, double scale, double x, double y) + { + var path = DrawingValidation.NormalizeModelPath(partFilePath); + DrawingValidation.RequireScale(scale); + if (!double.IsFinite(x) || !double.IsFinite(y)) + throw new ArgumentException("Координаты привязки вида должны быть конечными числами."); + if (!File.Exists(path)) + throw new FileNotFoundException($"Файл модели не найден: {path}", path); + if (new FileInfo(path).Length == 0) + throw new InvalidOperationException($"Файл модели пуст (не сохранён?): {path}"); + + var views = RequireActiveDrawingViews(); + // Снимок номеров существующих видов — чтобы потом отличить созданные ИМЕННО этим вызовом + // (на чертеже уже есть системный вид и могут быть прежние виды). + var beforeNumbers = CollectViewNumbers(views); + + if (!views.AddStandartViews(path, "#Спереди", StandardProjections, x, y, scale, GapMm, GapMm)) + throw new InvalidOperationException( + $"AddStandartViews вернул FALSE — виды не созданы (файл модели или проекция недоступны): {path}"); + + var newViews = CollectNewViews(views, beforeNumbers); + try + { + if (newViews.Count != ExpectedCreated) + throw new InvalidOperationException( + $"Ожидалось {ExpectedCreated} вида, создано {newViews.Count}."); + + // Гарантия непустоты: у созданных видов должна быть геометрия (иначе модель не + // спроецировалась). Проверяем ИМЕННО новые виды, не всю коллекцию. + if (newViews.Sum(v => v.ObjectCount) <= 0) + throw new InvalidOperationException( + "Стандартные виды созданы, но пусты (модель не спроецировалась). Проверьте файл модели."); + + return new DrawingViewsResult { Created = newViews.Count, Total = views.Count }; + } + catch + { + // Откат: удаляем виды, созданные этим вызовом, чтобы чертёж не остался в полу-состоянии. + foreach (var v in newViews) + try { v.Delete(); } catch { /* лучшее усилие */ } + throw; + } + } + + private IViews RequireActiveDrawingViews() + { + var doc = _session.Application.ActiveDocument + ?? throw new InvalidOperationException("Нет активного документа. Создайте чертёж: document_create drawing."); + if (doc.DocumentType != DocumentTypeEnum.ksDocumentDrawing) + throw new InvalidOperationException( + $"Активный документ не является чертежом (тип: {doc.DocumentType}). Создайте чертёж: document_create drawing."); + if (doc is not IKompasDocument2D doc2d) + throw new InvalidOperationException("Активный документ не приводится к IKompasDocument2D."); + return doc2d.ViewsAndLayersManager?.Views + ?? throw new InvalidOperationException("Не удалось получить коллекцию видов чертежа."); + } + + private static HashSet CollectViewNumbers(IViews views) + { + var set = new HashSet(); + foreach (var o in views) + if (o is IView v) + set.Add(v.Number); + return set; + } + + private static List CollectNewViews(IViews views, HashSet beforeNumbers) + { + var list = new List(); + foreach (var o in views) + if (o is IView v && !beforeNumbers.Contains(v.Number)) + list.Add(v); + return list; + } +} diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs b/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs new file mode 100644 index 0000000..bb9d0c3 --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs @@ -0,0 +1,35 @@ +namespace Kompas.Mcp.Core.Drawings; + +/// Чистые валидаторы операций чертежа (без COM) — выделены для unit-тестов. +public static class DrawingValidation +{ + /// Допустимые расширения файла-источника модели: деталь (.m3d) и сборка (.a3d). + private static readonly HashSet AllowedExtensions = + new(StringComparer.OrdinalIgnoreCase) { ".m3d", ".a3d" }; + + /// + /// Нормализовать путь к файлу 3D-модели: обрезать пробелы, проверить непустоту и расширение + /// (.m3d/.a3d), вернуть абсолютный путь. Существование файла НЕ проверяет (это делает сервис). + /// + public static string NormalizeModelPath(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException("Путь к файлу модели не задан.", nameof(path)); + + var trimmed = path.Trim(); + var ext = Path.GetExtension(trimmed); + if (!AllowedExtensions.Contains(ext)) + throw new ArgumentException( + $"Недопустимое расширение файла модели '{ext}'. Ожидается .m3d (деталь) или .a3d (сборка).", + nameof(path)); + + return Path.GetFullPath(trimmed); + } + + /// Проверить масштаб чертежа: конечный и строго положительный. + public static void RequireScale(double scale) + { + if (!double.IsFinite(scale) || scale <= 0) + throw new ArgumentOutOfRangeException(nameof(scale), "Масштаб должен быть конечным и > 0."); + } +} diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs b/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs new file mode 100644 index 0000000..336cec7 --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs @@ -0,0 +1,11 @@ +namespace Kompas.Mcp.Core.Drawings; + +/// Результат создания стандартных видов на чертеже. +public sealed record DrawingViewsResult +{ + /// Сколько видов создано этим вызовом. + public required int Created { get; init; } + + /// Общее число видов на чертеже после операции (включая системный вид). + public required int Total { get; init; } +} diff --git a/src/Kompas.Mcp.Host/Program.cs b/src/Kompas.Mcp.Host/Program.cs index 7b17e3a..6a047c1 100644 --- a/src/Kompas.Mcp.Host/Program.cs +++ b/src/Kompas.Mcp.Host/Program.cs @@ -2,6 +2,7 @@ using Kompas.Mcp.Core; using Kompas.Mcp.Core.Assemblies; using Kompas.Mcp.Core.Conversion; using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; using Kompas.Mcp.Core.Editing; using Kompas.Mcp.Core.Modeling; using Kompas.Mcp.Core.Query; @@ -25,6 +26,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); diff --git a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs new file mode 100644 index 0000000..d96ab9b --- /dev/null +++ b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs @@ -0,0 +1,29 @@ +using System.ComponentModel; +using Kompas.Mcp.Core; +using Kompas.Mcp.Core.Drawings; +using ModelContextProtocol.Server; + +namespace Kompas.Mcp.Host.Tools; + +/// Инструменты 2D-чертежа (виды, оформление). +[McpServerToolType] +public sealed class DrawingTools(KompasSession session, DrawingService drawing) +{ + [McpServerTool(Name = "drawing_create_standard_views")] + [Description("На активном чертеже создать стандартные ассоциативные виды (спереди, сверху, слева) " + + "сохранённой 3D-модели (.m3d деталь / .a3d сборка). Активный документ должен быть чертежом " + + "(document_create drawing); модель должна быть сохранена в файл (document_save_as). " + + "scale — масштаб (1.0 = 1:1); x,y — точка привязки главного вида на листе (мм). " + + "Повторный вызов добавит ещё один набор видов — задайте другие x,y, чтобы не накладывались. " + + "Возвращает число созданных видов.")] + public async Task CreateStandardViews( + [Description("Путь к файлу 3D-модели (.m3d/.a3d)")] string partFilePath, + [Description("Масштаб видов (1.0 = 1:1)")] double scale = 1.0, + [Description("X привязки главного вида на листе, мм")] double x = 100, + [Description("Y привязки главного вида на листе, мм")] double y = 150) + { + await session.ConnectAsync(); + var r = await drawing.CreateStandardViewsAsync(partFilePath, scale, x, y); + return $"Создано {r.Created} стандартных вида на чертеже (всего видов: {r.Total}, масштаб {scale})."; + } +} diff --git a/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs b/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs new file mode 100644 index 0000000..0b5ee7b --- /dev/null +++ b/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs @@ -0,0 +1,56 @@ +using Kompas.Mcp.Core.Drawings; + +namespace Kompas.Mcp.Tests; + +[Trait("Category", "Unit")] +public sealed class DrawingValidationTests +{ + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void NormalizeModelPath_throws_on_empty(string? path) + => Assert.Throws(() => DrawingValidation.NormalizeModelPath(path)); + + [Theory] + [InlineData(@"C:\m\part.step")] + [InlineData(@"C:\m\part.txt")] + [InlineData(@"C:\m\part")] + public void NormalizeModelPath_throws_on_wrong_extension(string path) + => Assert.Throws(() => DrawingValidation.NormalizeModelPath(path)); + + [Theory] + [InlineData(@"C:\m\part.m3d", ".m3d")] + [InlineData(@"C:\m\frame.a3d", ".a3d")] + [InlineData(@" C:\m\part.M3D ", ".m3d")] + [InlineData(@"C:\модели\деталь.m3d", ".m3d")] // кириллица в пути + public void NormalizeModelPath_accepts_model_files(string path, string expectedExt) + { + var result = DrawingValidation.NormalizeModelPath(path); + Assert.True(Path.IsPathFullyQualified(result)); + Assert.EndsWith(expectedExt, result, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void NormalizeModelPath_preserves_cyrillic() + { + var result = DrawingValidation.NormalizeModelPath(@"C:\модели\деталь.m3d"); + Assert.Contains("деталь", result); + Assert.Contains("модели", result); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(double.NaN)] + [InlineData(double.PositiveInfinity)] + public void RequireScale_throws_on_non_positive_or_non_finite(double scale) + => Assert.Throws(() => DrawingValidation.RequireScale(scale)); + + [Theory] + [InlineData(1.0)] + [InlineData(0.5)] + [InlineData(2.0)] + public void RequireScale_accepts_positive(double scale) + => Assert.Null(Record.Exception(() => DrawingValidation.RequireScale(scale))); +} diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingTests.cs new file mode 100644 index 0000000..9908702 --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingTests.cs @@ -0,0 +1,88 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; +using Kompas.Mcp.Core.Modeling; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: стандартные ассоциативные виды чертежа (API7 IViews.AddStandartViews). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class DrawingTests : IntegrationTestBase +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly DrawingService _drawing; + + public DrawingTests(KompasFixture fx) : base(fx) + { + _docs = new DocumentService(fx.Session, fx.Dispatcher); + _modeler = new PartModeler(fx.Session, fx.Dispatcher); + _drawing = new DrawingService(fx.Session, fx.Dispatcher); + } + + private async Task BuildAndSaveBoxAsync() + { + var path = TestPaths.NewFile(".m3d"); + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + 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); + await _modeler.RebuildAsync(); + await _docs.SaveAsAsync(path); + } + finally { await _docs.CloseAsync(save: false); } + + Assert.True(File.Exists(path), $"Файл детали не сохранён: {path}"); + Assert.True(new FileInfo(path).Length > 0, "Файл детали пуст."); + return path; + } + + [Fact] + public async Task Creates_three_standard_views_on_drawing() + { + var partFile = await BuildAndSaveBoxAsync(); + + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var r = await _drawing.CreateStandardViewsAsync(partFile, scale: 1.0, x: 100, y: 150); + + // Создано ровно 3 вида (спереди/сверху/слева); всего на чертеже +3 к системному виду. + Assert.Equal(3, r.Created); + Assert.True(r.Total >= 4, $"Ожидалось ≥4 видов всего (системный + 3), получено {r.Total}."); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Rejects_non_drawing_document() + { + // Активный документ — деталь (не чертёж): понятная ошибка. + var partFile = await BuildAndSaveBoxAsync(); + + await _docs.CreateAsync(KompasDocumentType.Part); + try + { + await Assert.ThrowsAsync( + () => _drawing.CreateStandardViewsAsync(partFile, scale: 1.0, x: 100, y: 150)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Missing_model_file_throws() + { + var missing = Path.Combine(TestPaths.Scratch, $"no_such_{Guid.NewGuid():N}.m3d"); + + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await Assert.ThrowsAsync( + () => _drawing.CreateStandardViewsAsync(missing, scale: 1.0, x: 100, y: 150)); + } + finally { await _docs.CloseAsync(save: false); } + } +}