diff --git a/CLAUDE.md b/CLAUDE.md index 5041b64..318feae 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**. -**74 MCP tools, 188 tests green (123 unit + 65 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-чертёж (веха, инкремент 2):** **`drawing_create_standard_views`** (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`) — создать стандартные ассоциативные виды (спереди/сверху/слева) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже; возвращает число созданных видов (3). **`drawing_fill_title_block`** — заполнить графы основной надписи (штампа) активного чертежа: `designation` (обозначение, графа 2), `name` (наименование изделия, графа 1), `material` (материал, графа 3); передаются только нужные поля (null/пусто пропускается, нужно хотя бы одно); возвращает число заполненных граф. +**75 MCP tools, 203 tests green (131 unit + 72 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-чертёж (веха, инкремент 3):** **`drawing_create_standard_views`** (`DrawingService`, `DrawingTools`, namespace `Kompas.Mcp.Core.Drawings`) — создать стандартные ассоциативные виды (спереди/сверху/слева) сохранённой 3D-модели (.m3d/.a3d) на активном чертеже; возвращает число созданных видов (3) и их номера (`ViewNumbers`). **`drawing_fill_title_block`** — заполнить графы основной надписи (штампа) активного чертежа: `designation` (обозначение, графа 2), `name` (наименование изделия, графа 1), `material` (материал, графа 3); передаются только нужные поля (null/пусто пропускается, нужно хотя бы одно); возвращает число заполненных граф. **`drawing_add_linear_dimension`** — поставить линейный размер на виде активного чертежа: точки (x1,y1)-(x2,y2) в локальной СК вида (мм), положение размерной линии (x3,y3), `orientation` (horizontal|vertical|parallel), `viewNumber` (0=первый вид); значение измеряется автоматически; возвращает значение и номер вида. 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), `drawing_create_standard_views` (API7, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`), `drawing_fill_title_block` (API7, `DrawingService`; заполнение основной надписи/штампа через `IStamp`) 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/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`), `drawing_fill_title_block` (API7, `DrawingService`; заполнение основной надписи/штампа через `IStamp`), `drawing_add_linear_dimension` (API7, `DrawingService`; линейный размер на виде через `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`; координаты в локальной СК вида) 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,7 +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`, `drawing_fill_title_block`; `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)**: entry via API7 `IKompasDocument2D` — requires an active drawing document (`DocumentType==ksDocumentDrawing`). Standard views: `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}`. Confirmed live: `Views.Count` 1→4 (system view + 3 projections). **Title block / stamp** (`drawing_fill_title_block`): `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (indexed property, `IText.Str` replaces content — no `Clear` needed, overwrite confirmed) → `stamp.Update()`. Own enum `StampField {Designation, Name, Material}` + static `StampFields.ColumnId` (→ 2/1/3, `ksStampEnum`) and `Collect` (gathers non-empty trimmed fields). `RequireStampCell` validates null + `columnId>0`. `RequireActiveDrawing` shared with views check. Write is non-transactional (all cells → single `Update`). New file `src/Kompas.Mcp.Core/Drawings/StampField.cs`; tool added to existing `DrawingTools.cs`. DI: `AddSingleton()` in `Program.cs`. +- **Drawing service (`drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension`; `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)**: entry via API7 `IKompasDocument2D` — requires an active drawing document (`DocumentType==ksDocumentDrawing`). Standard views: `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,ViewNumbers}` — `ViewNumbers` contains the IView.Number values of the three created views (used to address views when placing dimensions). Confirmed live: `Views.Count` 1→4 (system view + 3 projections). **Title block / stamp** (`drawing_fill_title_block`): `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (indexed property, `IText.Str` replaces content — no `Clear` needed, overwrite confirmed) → `stamp.Update()`. Own enum `StampField {Designation, Name, Material}` + static `StampFields.ColumnId` (→ 2/1/3, `ksStampEnum`) and `Collect` (gathers non-empty trimmed fields). `RequireStampCell` validates null + `columnId>0`. `RequireActiveDrawing` shared with views check. Write is non-transactional (all cells → single `Update`). New file `src/Kompas.Mcp.Core/Drawings/StampField.cs`; tool added to existing `DrawingTools.cs`. **Linear dimension** (`drawing_add_linear_dimension`): places a linear dimension on a view of the active drawing. API7 path: locate view by `viewNumber` index → COM-QI `(ISymbols2DContainer)view` (dimension goes into that specific view, NOT to the sheet) → `LineDimensions.Add()` → `ILineDimension`: set `X1,Y1,X2,Y2` (extension point coordinates in view-local CS, mm), `X3,Y3` (dimension line position), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true` → `Update()` → check `Valid` → read value via `((IDimensionText)dim).NominalValue`. Coordinates are in **view-local coordinate system** (not sheet). Parallel orientation works without explicit Angle (auto from points). Rollback: `dim.Delete()` if `Update=FALSE` / `Valid=false` / zero NominalValue (orientation incompatible with points). **Key finding: dimensions are NOT counted in `IView.ObjectCount`** (which counts geometry in `IDrawingContainer`); dimensions live in `ISymbols2DContainer` separately — verify placement via `ISymbols2DContainer.LineDimensions.Count`. Own enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Validators `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. New files: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; tool in existing `DrawingTools.cs`. DI: `AddSingleton()` in `Program.cs`. - **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 2589b04..ea14b55 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ src/Kompas.Mcp.Host/bin/Release/net8.0-windows/kompas-mcp.exe } ``` -## Инструменты (74 инструмента) +## Инструменты (75 инструментов) | Группа | Инструменты | |---|---| @@ -54,7 +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`, `drawing_fill_title_block` | +| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension` | | Validation | `validate_part` | ### Типовой сценарий @@ -73,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/ 188 тестов: unit + integration (integration требуют КОМПАС) +tests/Kompas.Mcp.Tests/ 203 теста: 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 4c3e185..98a7bc1 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`+`drawing_create_standard_views`+`drawing_fill_title_block` (веха «2D-чертёж», инкремент 2)). Актуализировано по коду. +> Статус: **реализовано и работает** (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`+`drawing_fill_title_block`+`drawing_add_linear_dimension` (веха «2D-чертёж», инкремент 3)). Актуализировано по коду. > Сопутствующий контекст по 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, измерения), `AssemblyService` (вставка компонентов в сборку, namespace `Kompas.Mcp.Core.Assemblies`), `DrawingService` (стандартные виды чертежа, namespace `Kompas.Mcp.Core.Drawings`), снимок. Все паттерны API5/API7 живут здесь. | -| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 188 тестов (123 unit + 65 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | +| **Kompas.Mcp.Tests** | Юнит (без COM) + интеграционные (требуют КОМПАС). 203 теста (131 unit + 72 integration). Все интеграционные классы наследуют `IntegrationTestBase` (`IAsyncLifetime`), который вызывает `DocumentService.CloseAllAsync` после каждого теста. | Принцип: **только `Core` знает про COM**. `Host`/Tools оперируют доменными DTO и вызывают `Core`; `Host` не содержит бизнес-логики. @@ -121,7 +121,7 @@ MCP client ──stdio──> Host (поток-пул, async) --- -## 5. Карта инструментов (74 инструмента) +## 5. Карта инструментов (75 инструментов) Сгруппированы вокруг центрального цикла «эскиз ↔ операция». Имена — `snake_case`. @@ -209,8 +209,9 @@ MCP client ──stdio──> Host (поток-пул, async) - `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}`. Модель должна быть сохранена до вызова. +- `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,ViewNumbers}` — `ViewNumbers` содержит номера созданных видов (для адресации при простановке размеров). Модель должна быть сохранена до вызова. - `drawing_fill_title_block(designation?, name?, material?)` — заполнить графы основной надписи (штампа) активного чертежа. `designation` = обозначение документа (графа 2), `name` = наименование изделия (графа 1), `material` = материал (графа 3); нужно хотя бы одно поле. Реализовано: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (индексированное свойство; `IText.Str` замещает содержимое без `Clear`) → `stamp.Update()`. Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`. Возвращает число заполненных граф. +- `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — поставить линейный размер на виде активного чертежа. Координаты (x1,y1)-(x2,y2) — выносные точки в **локальной СК вида** (мм); (x3,y3) — положение размерной линии; `orientation`: `horizontal`|`vertical`|`parallel`; `viewNumber` — номер вида (0=первый). Значение измеряется автоматически (`AutoNominalValue=true`). Реализовано: `(ISymbols2DContainer)view` (COM-QI от конкретного `IView`, не от листа — размер попадает именно в этот вид) → `LineDimensions.Add()` → `ILineDimension` → `Update()` → `Valid` → `((IDimensionText)dim).NominalValue`. Ключевая находка: **размеры не входят в `IView.ObjectCount`** (он считает геометрию `IDrawingContainer`); проверять через `ISymbols2DContainer.LineDimensions.Count`. Откат через `dim.Delete()` при ошибке или нулевом значении. Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. **Vision — визуальная обратная связь (fallback)** - `model_snapshot` — отрендерить активный 3D-документ в PNG и вернуть **как image-контент MCP**, @@ -345,14 +346,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+drawing_create_standard_views+drawing_fill_title_block):** документы, эскизы (полный набор 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`); **`drawing_fill_title_block`** (заполнение основной надписи/штампа: обозначение, наименование, материал; `IStamp.Text[id].Str`, API7, `DrawingService`). +**Реализовано (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+drawing_fill_title_block+drawing_add_linear_dimension):** документы, эскизы (полный набор 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`); **`drawing_fill_title_block`** (заполнение основной надписи/штампа: обозначение, наименование, материал; `IStamp.Text[id].Str`, API7, `DrawingService`); **`drawing_add_linear_dimension`** (линейный размер на виде чертежа: `ISymbols2DContainer`/`LineDimensions.Add`/`ILineDimension`, координаты в локальной СК вида, 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`, …), рамка/формат, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`); основная надпись/штамп реализована (`drawing_fill_title_block`). +5. **2D-чертёж (продолжение):** диаметральные/радиальные/угловые размеры, рамка/формат, штриховки, тексты. Стандартные виды реализованы (`drawing_create_standard_views`); основная надпись/штамп реализована (`drawing_fill_title_block`); линейные размеры реализованы (`drawing_add_linear_dimension`). 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 16fa7d6..e220a76 100644 --- a/docs/OPEN_QUESTIONS.md +++ b/docs/OPEN_QUESTIONS.md @@ -194,3 +194,6 @@ stateful-сессии? Пока полагаемся на последовате ### ✅ Основная надпись (штамп) чертежа — реализована (инкремент 2 вехи «2D-чертёж») **Решено.** Новый инструмент `drawing_fill_title_block(designation?, name?, material?)` — заполняет графы основной надписи активного чертежа. Паттерн API7: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` → `stamp.Update()`. `IText.Str` замещает содержимое напрямую (повторная запись перезаписывает — `Clear` не нужен, подтверждено round-trip тестом). Номера граф `ksStampEnum`: обозначение=2, наименование=1, материал=3. Свой enum `StampField` + `StampFields.ColumnId/Collect` в `src/Kompas.Mcp.Core/Drawings/StampField.cs`; инструмент в `DrawingTools.cs`. Итог: **74 инструмента, 188 тестов (123 unit + 65 integration).** Следующий шаг: размеры на видах. + +### ✅ Линейные размеры на видах чертежа — реализованы (инкремент 3 вехи «2D-чертёж») +**Решено.** Новый инструмент `drawing_add_linear_dimension(x1,y1,x2,y2,x3,y3, orientation, viewNumber)` — ставит линейный размер на виде активного чертежа. Паттерн API7: получить вид по `viewNumber` → COM-QI `(ISymbols2DContainer)view` (размер адресуется конкретному виду, а не листу) → `LineDimensions.Add()` → `ILineDimension` → `X1,Y1,X2,Y2` (выносные точки в **локальной СК вида**, мм), `X3,Y3` (положение размерной линии), `Orientation` (`ksLineDimensionOrientationEnum`: `ksLinDParallel=0`/`ksLinDHorizontal=1`/`ksLinDVertical=2`), `AutoNominalValue=true` → `Update()` → `Valid` → значение `((IDimensionText)dim).NominalValue`. Откат `dim.Delete()` при ошибке или нулевом значении. **Ключевая находка: размеры НЕ входят в `IView.ObjectCount`** (тот считает геометрию `IDrawingContainer`); размеры живут в `ISymbols2DContainer` отдельно — попадание размера в вид проверяется через `ISymbols2DContainer.LineDimensions.Count`. `DrawingViewsResult` расширен полем `ViewNumbers` (номера созданных видов — для адресации при простановке размеров). Свой enum `DimensionOrientation {Horizontal, Vertical, Parallel}` + `DimensionOrientations.Parse/ToKompas`. Валидаторы `DrawingValidation.RequireFiniteCoords` + `RequireDistinctPoints`. Новые файлы: `DimensionOrientation.cs`, `DrawingDimensionResult.cs`; инструмент в `DrawingTools.cs`. Итог: **75 инструментов, 203 теста (131 unit + 72 integration).** Следующий шаг: диаметральные/радиальные/угловые размеры, рамка/формат. diff --git a/docs/presentation.html b/docs/presentation.html index 000f030..5c8fdc0 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+drawing_create_standard_views+drawing_fill_title_block (74 инструмента) + Статус: v3+ работает — эскиз→операции→STEP→сборка→move_face→структурный осмотр→богатые эскизы→shell+rib+sweep+loft+массивы+зеркало+hole+hole_counterbore+hole_countersink+hole_conic+draft+переменные+assembly_add_component+assembly_add_mate+drawing_create_standard_views+drawing_fill_title_block+drawing_add_linear_dimension (75 инструментов)

КОМПАС-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 · drawing_create_standard_views · drawing_fill_title_block · навык kompas-3d · 74 инструмента · 188 тестов
+
Общий прогрессv3+: STEP · move_face · структурный осмотр · богатые эскизы (пакет A) · shell+rib+sweep+loft (пакет B) · массивы+зеркало (пакет C) · переменные (пакет D start) · offset-plane (пакет E start) · hole (API7) · hole_counterbore · hole_countersink · hole_conic · draft (API5) · assembly_add_component · assembly_add_mate · drawing_create_standard_views · drawing_fill_title_block · drawing_add_linear_dimension · навык kompas-3d · 75 инструментов · 203 теста
@@ -297,7 +297,7 @@

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

-

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

+

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

  • System: connect · status · set_visible
  • Documents: create · open · save · save_as · close · active
  • @@ -310,8 +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)
  • -
  • Drawing: drawing_create_standard_views (стандартные ассоциативные виды чертежа, API7) · drawing_fill_title_block (основная надпись/штамп: обозначение, наименование, материал, API7)
  • -
  • 2D-чертёж: размеры/обозначения на видах, рамка/формат · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
  • +
  • Drawing: drawing_create_standard_views (стандартные ассоциативные виды чертежа, API7) · drawing_fill_title_block (основная надпись/штамп: обозначение, наименование, материал, API7) · drawing_add_linear_dimension (линейный размер на виде, ISymbols2DContainer, API7)
  • +
  • 2D-чертёж: диаметральные/радиальные/угловые размеры, рамка/формат · пакет E (ось, угловая плоскость) · пакет D параметрика: параметрические эскизы (API2D) — исследовано, недоступно через COM API · прочие типы сопряжений (parallel/perpendicular/concentric/angle/tangency)
@@ -322,7 +322,7 @@
  • Unit-тесты — без COM: диспетчер, enum-маппинг, StepFormat, InspectionText
  • Интеграционные тесты с КОМПАС: подключение, документы, снимок, цикл, STEP round-trip, сборка, move_face, инспекция
  • -
  • Итого 188 тестов зелёных (123 unit + 65 integration)
  • +
  • Итого 203 теста зелёных (131 unit + 72 integration)
  • Сборка kompas-mcp.exe, README с конфигом клиента
  • Навык kompas-3d (методика) + полигон usecases/
  • In-process тест MCP-клиента, релизный publish
  • @@ -351,8 +351,9 @@
    отверстия под крепёж · готово ✓

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

    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 тестов

    -
    2D-чертёж · инкремент 2 ✓

    Стандартные виды + основная надпись

    drawing_create_standard_views (AddStandartViews, API7) · drawing_fill_title_block (IStamp.Text[id].Str, API7) · 74 инструмента · 188 тестов · далее: размеры на видах

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

    Размеры и оформление

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

    +
    2D-чертёж · инкремент 2 ✓

    Стандартные виды + основная надпись

    drawing_create_standard_views (AddStandartViews, API7) · drawing_fill_title_block (IStamp.Text[id].Str, API7) · 74 инструмента · 188 тестов

    +
    2D-чертёж · инкремент 3 ✓

    Линейные размеры

    drawing_add_linear_dimension (ISymbols2DContainer/LineDimensions.Add/ILineDimension, локальная СК вида, API7) · ViewNumbers в результате видов · 75 инструментов · 203 теста · далее: диаметральные/радиальные размеры, рамка/формат

    +
    далее · в работе ▶

    Оформление чертежа

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

diff --git a/docs/superpowers/specs/2026-05-27-drawing-linear-dimension-design.md b/docs/superpowers/specs/2026-05-27-drawing-linear-dimension-design.md new file mode 100644 index 0000000..7097db6 --- /dev/null +++ b/docs/superpowers/specs/2026-05-27-drawing-linear-dimension-design.md @@ -0,0 +1,138 @@ +# Дизайн: линейный размер на виде чертежа (drawing_add_linear_dimension) через API7 + +**Дата:** 2026-05-27 +**Статус:** реализовано и проверено (7 интеграционных + unit, всего 203); ревью Codex спека и реализации учтено + +## Правки по ревью реализации Codex + +- **#4 нулевое значение** (важно): после `Update`+`Valid` проверяем `|NominalValue| > 1e-6` — ловит + несовместимость ориентации с точками (horizontal для точек на одной вертикали: `|X2-X1|=0`), которую + `RequireDistinctPoints` не видит. При нуле — откат (`dim.Delete`) + ошибка. Тест добавлен. +- **#3 Parallel** (важно): добавлен интеграционный тест — параллельный (0,0)-(30,40) → 50 (3-4-5). + **Работает БЕЗ явного `Angle`** (КОМПАС авто-вычисляет угол по точкам) — Parallel оставлен. +- **#7 rollback** (важно): тест нулевой проекции проходит через ветку `dim.Delete()` (откат) — + проверяет, что число размеров вида не изменилось. (Update==false/Valid==false без fault injection.) +- **#8 ассоциативность вида** (минор): осознанный контракт «первый НЕсистемный вид» (задокументирован + в описании инструмента и ошибке FindView). + +## Находка реализации: размеры НЕ в IView.ObjectCount + +`IView.ObjectCount` считает **геометрию** (`IDrawingContainer`: линии/дуги), а размеры живут в +`ISymbols2DContainer` отдельно → после простановки размера `ObjectCount` НЕ меняется (проверено: 4→4). +Поэтому «размер попал в целевой вид» проверяем счётом `ISymbols2DContainer.LineDimensions.Count` +(helper `GetViewLineDimensionCountAsync`), а не `ObjectCount`. (Гарантия непустоты у стандартных видов +по `ObjectCount` остаётся верной — там именно геометрия.) + +## Правки по ревью Codex + +- **Нулевая длина** (#1): `DrawingValidation.RequireDistinctPoints(x1,y1,x2,y2)` — расстояние между + точками > 1e-7 (иначе вырожденный размер); + проверка `dim.Valid` после `Update()` (откат); + + негативный тест. +- **Контракт вида** (#4): честно — «первый НЕСИСТЕМНЫЙ вид (Number != 0)» при `viewNumber<=0`, без + претензии на ассоциативность и без QI к `IAssociationView` (в наших чертежах иных видов нет). +- **null-check + порядок** (#9): `LineDimensions.Add()` проверять на null; `AutoNominalValue=true` + выставлять явно; `NominalValue` читать ТОЛЬКО после `Update()==true && Valid==true`. +- **ViewNumbers** (#7): поле `required` в `DrawingViewsResult`; инициализатор и текст MCP-ответа + `drawing_create_standard_views` обновить (вернуть номера видов). +- **Тесты** (#6): ObjectCount ЦЕЛЕВОГО вида до/после; адресация конкретного `viewNumber`; негатив на + совпадающие точки. **viewNumber не найден** (#2) → понятная ошибка. 3D-конвертер точек (#8) — будущее. + +## Цель + +Веха 2D-ЧЕРТЁЖ, инкремент 3 (размеры). После видов и основной надписи — **линейный размер** на +ассоциативном виде: между двумя точками (в локальной СК вида) с авто-измерением длины. Размеры — +самый ценный элемент оформления (несут размерную информацию). Начинаем с линейного +(горизонтальный/вертикальный/параллельный); диаметральные/радиальные/угловые — далее. + +## Спайк: механизм подтверждён вживую (НЕ разучивать) + +Спайк (`DrawingDimensionSpikeTests`, зелёный) подтвердил путь API7: + +``` +IView view = первый вид с Number != 0; // системный вид — #0, пропускаем +ISymbols2DContainer symbols = (ISymbols2DContainer)view; // COM-QI ОТ ВИДА (не от документа!) +ILineDimension dim = symbols.LineDimensions.Add(); +dim.X1=x1; dim.Y1=y1; dim.X2=x2; dim.Y2=y2; // выносные точки (ЛОКАЛЬНАЯ СК вида, мм) +dim.X3=x3; dim.Y3=y3; // положение размерной линии +dim.Orientation = ksLinDHorizontal; // 0=Parallel / 1=Horizontal / 2=Vertical +bool ok = dim.Update(); +double value = ((IDimensionText)dim).NominalValue; // авто-измеренное значение (мм) +``` + +**Проверено (вид спереди коробки 40×30×20):** размер (0,0)-(40,0) горизонтальный → +`Update()=True`, `Valid=True`, `NominalValue == 40` (авто `|X2-X1|`), `view.ObjectCount` вырос. + +**Подтверждённые факты:** +- **`ISymbols2DContainer` берётся COM-QI ОТ КОНКРЕТНОГО `IView`** — размер попадает именно в этот + вид (не нужен `Current=true`). (Аналогично 3D: `part`→`IModelContainer`.) +- **Координаты X1..Y3 — ЛОКАЛЬНАЯ СК вида** (= координаты модели на плоскости вида, при масштабе + 1:1 совпадают с мм модели). НЕ координаты листа. +- **`AutoNominalValue=true` по умолчанию** → `NominalValue` = расстояние между точками + (горизонтальный: `|X2-X1|`, вертикальный: `|Y2-Y1|`, параллельный: реальная длина). Значение + читается `((IDimensionText)dim).NominalValue` (QI). +- `Orientation` (`ksLineDimensionOrientationEnum`): `ksLinDParallel=0`, `ksLinDHorizontal=1`, + `ksLinDVertical=2` (в `Kompas6Constants`). +- `Update()` обязателен; перестроение документа не нужно. + +## Адресация вида (часть инкремента) + +Чтобы указать, НА КАКОМ виде ставить размер, нужен его `Number`. Сейчас +`drawing_create_standard_views` возвращает только `{Created, Total}`. **Расширяем `DrawingViewsResult` +полем `ViewNumbers` (номера созданных видов)** — уже собираются как `newViews` в +`CreateStandardViewsCore` (тривиально: `newViews.Select(v => v.Number)`). Это закрывает замечание +Codex из ревью видов (#10: «нет ссылок на созданные виды») и делает размеры адресуемыми. + +## MCP-инструмент + +| Инструмент | Параметры | Поведение | +|---|---|---| +| `drawing_add_linear_dimension` | `x1,y1,x2,y2` (выносные точки), `x3,y3` (положение размерной линии), `orientation="horizontal"`, `viewNumber=0` | Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ СК вида (мм). `orientation`: horizontal/vertical/parallel. `viewNumber`: номер вида (0 = первый ассоциативный/главный). Значение измеряется автоматически. Возвращает измеренное значение и номер вида. | + +Контракт: активный документ — чертёж; на чертеже есть хотя бы один ассоциативный вид. Координаты — +локальные вида (см. выше). `drawing_create_standard_views` возвращает `ViewNumbers` для адресации. + +## Архитектура + +В существующем `DrawingService`. Свой enum `DimensionOrientation` (паттерн `MateType`/`StampField`). + +- `src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs` — enum + static `DimensionOrientations` + (`Parse(string)`, `ToKompas` → `ksLineDimensionOrientationEnum`). +- `DrawingService.AddLinearDimensionAsync(viewNumber, x1,y1,x2,y2,x3,y3, DimensionOrientation, ct)`. +- `DrawingViewsResult` += `IReadOnlyList ViewNumbers`. +- Инструмент `drawing_add_linear_dimension` в `DrawingTools` (orientation строкой → Parse). + +## Валидация (чистые static, unit-тест) + +- `DimensionOrientations.Parse` — horizontal/vertical/parallel (+рус.) → enum; иначе `ArgumentException`. +- `DimensionOrientations.ToKompas` — enum → `ksLineDimensionOrientationEnum` (0/1/2). +- `DrawingValidation.RequireFiniteCoords(params)` — координаты конечны (NaN/Infinity → ошибка). + +## Реализация + +`AddLinearDimensionCore`: валидация координат + orientation → `RequireActiveDrawing` → найти вид +(`viewNumber<=0` → первый с `Number!=0`; иначе по `Number`, ошибка если нет) → `(ISymbols2DContainer)view` +→ `LineDimensions.Add()` → задать X1..Y3 + Orientation → `Update()` (FALSE → откат `dim.Delete()` + +ошибка) → проверить `dim.Valid` (false → откат + ошибка) → вернуть `AddedDimension { Value = +((IDimensionText)dim).NominalValue, ViewNumber = view.Number }`. RCW точечно не освобождаем (v2-2). + +## Тестирование + +### Unit (`DimensionOrientationsTests`) +- `Parse` — horizontal/vertical/parallel и рус.-синонимы → enum; бросает на мусоре. +- `ToKompas` — каждый → 1/2/0. +- `RequireFiniteCoords` — бросает на NaN/Infinity; пропускает конечные. + +### Integration (`DrawingDimensionTests`) +Паттерн: построить деталь → сохранить → создать чертёж → стандартные виды → размер → проверить → +`finally CloseAsync` (база `IntegrationTestBase`). +1. **Горизонтальный** (0,0)-(40,0) → `Value ≈ 40`; `view.ObjectCount` вырос. +2. **Вертикальный** (0,0)-(0,20) → `Value ≈ 20`. +3. **Адресация вида**: `CreateStandardViewsAsync` вернул `ViewNumbers` (3 шт); размер с конкретным + `viewNumber` ставится в этот вид. +4. **Нет видов**: чертёж без видов → понятная ошибка. + +(Сценарий 1 = промотированный спайк; спайк-файл удаляется.) + +## Дальнейшее (вне спека) +- Диаметральные/радиальные/угловые размеры; привязка к геометрии вида (ассоциативно, `BaseObject`); + допуски/обозначения; рамка/формат листа. diff --git a/src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs b/src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs new file mode 100644 index 0000000..29e6e8f --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/DimensionOrientation.cs @@ -0,0 +1,41 @@ +using Kompas6Constants; + +namespace Kompas.Mcp.Core.Drawings; + +/// Ориентация линейного размера (обёртка над ). +public enum DimensionOrientation +{ + /// Горизонтальный (измеряет |X2−X1|). + Horizontal, + /// Вертикальный (измеряет |Y2−Y1|). + Vertical, + /// Параллельный отрезку P1→P2 (реальная длина). + Parallel, +} + +/// Маппинг ориентации размера ↔ перечисление КОМПАС и разбор строки. +public static class DimensionOrientations +{ + public static ksLineDimensionOrientationEnum ToKompas(DimensionOrientation orientation) => orientation switch + { + DimensionOrientation.Horizontal => ksLineDimensionOrientationEnum.ksLinDHorizontal, + DimensionOrientation.Vertical => ksLineDimensionOrientationEnum.ksLinDVertical, + DimensionOrientation.Parallel => ksLineDimensionOrientationEnum.ksLinDParallel, + _ => throw new ArgumentOutOfRangeException(nameof(orientation), orientation, "Неизвестная ориентация размера."), + }; + + /// Разобрать строку ориентации (англ./рус.) в . + public static DimensionOrientation Parse(string value) + { + ArgumentNullException.ThrowIfNull(value); + return value.Trim().ToLowerInvariant() switch + { + "horizontal" or "h" or "горизонтальный" or "горизонтальная" => DimensionOrientation.Horizontal, + "vertical" or "v" or "вертикальный" or "вертикальная" => DimensionOrientation.Vertical, + "parallel" or "p" or "параллельный" or "параллельная" => DimensionOrientation.Parallel, + _ => throw new ArgumentException( + $"Неизвестная ориентация размера '{value}'. Допустимо: horizontal, vertical, parallel.", + nameof(value)), + }; + } +} diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs b/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs new file mode 100644 index 0000000..3ccc082 --- /dev/null +++ b/src/Kompas.Mcp.Core/Drawings/DrawingDimensionResult.cs @@ -0,0 +1,11 @@ +namespace Kompas.Mcp.Core.Drawings; + +/// Результат простановки размера на чертеже. +public sealed record DrawingDimensionResult +{ + /// Измеренное значение размера (мм). + public required double Value { get; init; } + + /// Номер вида, на котором поставлен размер. + public required int ViewNumber { get; init; } +} diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs index 7a2d6b8..ee0023f 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingService.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingService.cs @@ -84,6 +84,85 @@ public sealed class DrawingService ?? throw new InvalidOperationException("У листа оформления нет основной надписи (Stamp)."); } + /// + /// Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) в ЛОКАЛЬНОЙ + /// СК вида (мм); размерная линия проходит через (x3,y3). — номер вида + /// (≤0 = первый несистемный/главный). Значение измеряется автоматически. Возвращает значение и номер вида. + /// + public Task AddLinearDimensionAsync(int viewNumber, double x1, double y1, + double x2, double y2, double x3, double y3, DimensionOrientation orientation, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + AddLinearDimensionCore(viewNumber, x1, y1, x2, y2, x3, y3, orientation), ct); + + private DrawingDimensionResult AddLinearDimensionCore(int viewNumber, double x1, double y1, + double x2, double y2, double x3, double y3, DimensionOrientation orientation) + { + DrawingValidation.RequireFiniteCoords(x1, y1, x2, y2, x3, y3); + DrawingValidation.RequireDistinctPoints(x1, y1, x2, y2); + + var view = FindView(viewNumber); + var symbols = view as ISymbols2DContainer + ?? throw new InvalidOperationException("Вид не приводится к ISymbols2DContainer."); + var dim = symbols.LineDimensions?.Add() + ?? throw new InvalidOperationException("LineDimensions.Add вернул null."); + try + { + dim.X1 = x1; dim.Y1 = y1; dim.X2 = x2; dim.Y2 = y2; dim.X3 = x3; dim.Y3 = y3; + dim.Orientation = DimensionOrientations.ToKompas(orientation); + ((IDimensionText)dim).AutoNominalValue = true; // авто-измерение значения (по умолчанию, явно) + + if (!dim.Update()) + throw new InvalidOperationException("ILineDimension.Update вернул FALSE — размер не построен."); + if (!dim.Valid) + throw new InvalidOperationException("Размер построен невалидным (проверьте точки/ориентацию)."); + + // NominalValue читаем ТОЛЬКО после успешного Update и Valid. + var value = ((IDimensionText)dim).NominalValue; + // Нулевое значение — ориентация несовместима с точками (напр. horizontal для точек на одной + // вертикали: |X2-X1|=0). RequireDistinctPoints это не ловит — точки различны по Y. + if (Math.Abs(value) < 1e-6) + throw new InvalidOperationException( + "Размер измерил нулевое значение — выбранная ориентация несовместима с точками " + + "(например, horizontal для точек на одной вертикали; используйте vertical/parallel)."); + return new DrawingDimensionResult { Value = value, ViewNumber = view.Number }; + } + catch + { + try { dim.Delete(); } catch { /* лучшее усилие */ } + throw; + } + } + + /// Число линейных размеров на виде по номеру (для проверки/диагностики). Размеры живут + /// в ISymbols2DContainer вида, а НЕ в IView.ObjectCount (тот считает геометрию). + public Task GetViewLineDimensionCountAsync(int viewNumber, CancellationToken ct = default) + => _dispatcher.InvokeAsync(() => + { + var symbols = (ISymbols2DContainer)FindView(viewNumber); + return symbols.LineDimensions?.Count ?? 0; + }, ct); + + /// Найти вид: при viewNumber ≤ 0 — первый несистемный (Number != 0); иначе по номеру. + private IView FindView(int viewNumber) + { + var views = RequireActiveDrawingViews(); + foreach (var o in views) + { + if (o is not IView v) continue; + if (viewNumber <= 0) + { + if (v.Number != 0) return v; // первый несистемный + } + else if (v.Number == viewNumber) + { + return v; + } + } + throw new InvalidOperationException(viewNumber <= 0 + ? "На чертеже нет несистемного вида. Сначала создайте виды: drawing_create_standard_views." + : $"Вид с номером {viewNumber} не найден на чертеже."); + } + // Стандартная тройка проекций: ksPtFront=1, ksPtUp=3, ksPtLeft=5 (ksRelativeProjectionTypeEnum). private static readonly object[] StandardProjections = { 1, 3, 5 }; private const int ExpectedCreated = 3; @@ -122,7 +201,12 @@ public sealed class DrawingService throw new InvalidOperationException( "Стандартные виды созданы, но пусты (модель не спроецировалась). Проверьте файл модели."); - return new DrawingViewsResult { Created = newViews.Count, Total = views.Count }; + return new DrawingViewsResult + { + Created = newViews.Count, + Total = views.Count, + ViewNumbers = newViews.Select(v => v.Number).ToArray(), + }; } catch { diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs b/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs index bb9d0c3..032a2bc 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingValidation.cs @@ -32,4 +32,26 @@ public static class DrawingValidation if (!double.IsFinite(scale) || scale <= 0) throw new ArgumentOutOfRangeException(nameof(scale), "Масштаб должен быть конечным и > 0."); } + + /// Проверить, что все координаты конечны (отсекает NaN/Infinity). + public static void RequireFiniteCoords(params double[] coords) + { + foreach (var c in coords) + if (!double.IsFinite(c)) + throw new ArgumentException("Координаты должны быть конечными числами (не NaN/Infinity)."); + } + + /// Минимальное расстояние между выносными точками линейного размера (мм). + private const double MinPointDistance = 1e-7; + + /// + /// Проверить, что выносные точки размера различны (иначе размер вырожден — нулевая длина). + /// + public static void RequireDistinctPoints(double x1, double y1, double x2, double y2) + { + var dx = x2 - x1; + var dy = y2 - y1; + if (Math.Sqrt(dx * dx + dy * dy) < MinPointDistance) + throw new ArgumentException("Выносные точки размера совпадают — нельзя поставить размер нулевой длины."); + } } diff --git a/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs b/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs index 336cec7..fb36be9 100644 --- a/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs +++ b/src/Kompas.Mcp.Core/Drawings/DrawingViewsResult.cs @@ -8,4 +8,7 @@ public sealed record DrawingViewsResult /// Общее число видов на чертеже после операции (включая системный вид). public required int Total { get; init; } + + /// Номера созданных видов (для адресации при простановке размеров). + public required IReadOnlyList ViewNumbers { get; init; } } diff --git a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs index 9ca2ad8..9341593 100644 --- a/src/Kompas.Mcp.Host/Tools/DrawingTools.cs +++ b/src/Kompas.Mcp.Host/Tools/DrawingTools.cs @@ -24,7 +24,9 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing) { await session.ConnectAsync(); var r = await drawing.CreateStandardViewsAsync(partFilePath, scale, x, y); - return $"Создано {r.Created} стандартных вида на чертеже (всего видов: {r.Total}, масштаб {scale})."; + var numbers = string.Join(", ", r.ViewNumbers); + return $"Создано {r.Created} стандартных вида на чертеже (всего видов: {r.Total}, масштаб {scale}). " + + $"Номера созданных видов: {numbers} (для drawing_add_linear_dimension)."; } [McpServerTool(Name = "drawing_fill_title_block")] @@ -41,4 +43,26 @@ public sealed class DrawingTools(KompasSession session, DrawingService drawing) var filled = await drawing.FillTitleBlockAsync(designation, name, material); return $"Заполнено граф основной надписи: {filled}."; } + + [McpServerTool(Name = "drawing_add_linear_dimension")] + [Description("Поставить линейный размер на виде активного чертежа между точками (x1,y1)-(x2,y2) " + + "в ЛОКАЛЬНОЙ СК вида (мм; при масштабе 1:1 = координаты модели на плоскости вида). Размерная " + + "линия проходит через (x3,y3). orientation: horizontal (|X2-X1|) | vertical (|Y2-Y1|) | parallel " + + "(длина отрезка). viewNumber — номер вида из drawing_create_standard_views (0 = первый/главный вид). " + + "Значение измеряется автоматически. Возвращает измеренное значение и номер вида.")] + public async Task AddLinearDimension( + [Description("X первой выносной точки (СК вида, мм)")] double x1, + [Description("Y первой выносной точки")] double y1, + [Description("X второй выносной точки")] double x2, + [Description("Y второй выносной точки")] double y2, + [Description("X положения размерной линии")] double x3, + [Description("Y положения размерной линии")] double y3, + [Description("Ориентация: horizontal | vertical | parallel")] string orientation = "horizontal", + [Description("Номер вида (0 = первый/главный)")] int viewNumber = 0) + { + await session.ConnectAsync(); + var o = DimensionOrientations.Parse(orientation); + var r = await drawing.AddLinearDimensionAsync(viewNumber, x1, y1, x2, y2, x3, y3, o); + return $"Линейный размер ({orientation}) поставлен на виде {r.ViewNumber}, значение {r.Value:0.###} мм."; + } } diff --git a/tests/Kompas.Mcp.Tests/DimensionOrientationsTests.cs b/tests/Kompas.Mcp.Tests/DimensionOrientationsTests.cs new file mode 100644 index 0000000..5cdeaeb --- /dev/null +++ b/tests/Kompas.Mcp.Tests/DimensionOrientationsTests.cs @@ -0,0 +1,24 @@ +using Kompas.Mcp.Core.Drawings; + +namespace Kompas.Mcp.Tests; + +[Trait("Category", "Unit")] +public sealed class DimensionOrientationsTests +{ + [Theory] + [InlineData("horizontal", DimensionOrientation.Horizontal)] + [InlineData("vertical", DimensionOrientation.Vertical)] + [InlineData("parallel", DimensionOrientation.Parallel)] + [InlineData("ГОРИЗОНТАЛЬНЫЙ", DimensionOrientation.Horizontal)] + [InlineData(" vertical ", DimensionOrientation.Vertical)] + public void Parse_maps_known(string value, DimensionOrientation expected) + => Assert.Equal(expected, DimensionOrientations.Parse(value)); + + [Fact] + public void Parse_throws_on_unknown() + => Assert.Throws(() => DimensionOrientations.Parse("diagonal")); + + // ToKompas (→ ksLineDimensionOrientationEnum) намеренно НЕ покрыт unit-тестом: тестовый проект + // не ссылается на Kompas6Constants (граница проекта). Маппинг тривиален и проверяется интеграцией + // (horizontal → |X2-X1|, vertical → |Y2-Y1|). +} diff --git a/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs b/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs index 0b5ee7b..ab20534 100644 --- a/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs +++ b/tests/Kompas.Mcp.Tests/DrawingValidationTests.cs @@ -53,4 +53,19 @@ public sealed class DrawingValidationTests [InlineData(2.0)] public void RequireScale_accepts_positive(double scale) => Assert.Null(Record.Exception(() => DrawingValidation.RequireScale(scale))); + + [Fact] + public void RequireFiniteCoords_throws_on_non_finite() + { + Assert.Throws(() => DrawingValidation.RequireFiniteCoords(0, double.NaN)); + Assert.Throws(() => DrawingValidation.RequireFiniteCoords(double.PositiveInfinity)); + Assert.Null(Record.Exception(() => DrawingValidation.RequireFiniteCoords(0, 1, -5, 40))); + } + + [Fact] + public void RequireDistinctPoints_throws_on_coincident() + { + Assert.Throws(() => DrawingValidation.RequireDistinctPoints(10, 20, 10, 20)); + Assert.Null(Record.Exception(() => DrawingValidation.RequireDistinctPoints(0, 0, 40, 0))); + } } diff --git a/tests/Kompas.Mcp.Tests/Integration/DrawingDimensionTests.cs b/tests/Kompas.Mcp.Tests/Integration/DrawingDimensionTests.cs new file mode 100644 index 0000000..3ee34dc --- /dev/null +++ b/tests/Kompas.Mcp.Tests/Integration/DrawingDimensionTests.cs @@ -0,0 +1,159 @@ +using Kompas.Mcp.Core.Documents; +using Kompas.Mcp.Core.Drawings; +using Kompas.Mcp.Core.Modeling; + +namespace Kompas.Mcp.Tests.Integration; + +/// Интеграция: линейный размер на виде чертежа (API7 ISymbols2DContainer.LineDimensions). +[Trait("Category", "Integration")] +[Collection(KompasCollection.Name)] +public sealed class DrawingDimensionTests : IntegrationTestBase +{ + private readonly DocumentService _docs; + private readonly PartModeler _modeler; + private readonly DrawingService _drawing; + + public DrawingDimensionTests(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); } + return path; + } + + [Fact] + public async Task Horizontal_dimension_auto_measures_distance() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + Assert.Equal(3, views.ViewNumbers.Count); + + var r = await _drawing.AddLinearDimensionAsync( + viewNumber: 0, x1: 0, y1: 0, x2: 40, y2: 0, x3: 20, y3: -10, DimensionOrientation.Horizontal); + + Assert.InRange(r.Value, 39.9, 40.1); // авто-значение = |X2-X1| + Assert.Contains(r.ViewNumber, views.ViewNumbers); // размер попал в один из видов + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Vertical_dimension_measures_y_span() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var r = await _drawing.AddLinearDimensionAsync( + viewNumber: 0, x1: 0, y1: 0, x2: 0, y2: 20, x3: -10, y3: 10, DimensionOrientation.Vertical); + Assert.InRange(r.Value, 19.9, 20.1); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Targets_specific_view_by_number_and_increments_its_object_count() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + var views = await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var targetNumber = views.ViewNumbers[1]; // второй созданный вид (напр. сверху) + + // Размеры живут в ISymbols2DContainer вида — считаем именно их у ЦЕЛЕВОГО вида. + var before = await _drawing.GetViewLineDimensionCountAsync(targetNumber); + var r = await _drawing.AddLinearDimensionAsync( + viewNumber: targetNumber, x1: 0, y1: 0, x2: 40, y2: 0, x3: 20, y3: -10, DimensionOrientation.Horizontal); + var after = await _drawing.GetViewLineDimensionCountAsync(targetNumber); + + Assert.Equal(targetNumber, r.ViewNumber); + Assert.Equal(before + 1, after); // размер попал именно в целевой вид + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Parallel_dimension_measures_true_length() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + // Параллельный размер (0,0)-(30,40): реальная длина = 50 (3-4-5). + var r = await _drawing.AddLinearDimensionAsync( + viewNumber: 0, x1: 0, y1: 0, x2: 30, y2: 40, x3: 25, y3: 5, DimensionOrientation.Parallel); + Assert.InRange(r.Value, 49.5, 50.5); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Zero_projection_orientation_throws_and_rolls_back() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + var before = await _drawing.GetViewLineDimensionCountAsync(0); + + // Горизонтальный размер для точек на одной вертикали → |X2-X1|=0 → ошибка + откат. + await Assert.ThrowsAsync(() => _drawing.AddLinearDimensionAsync( + viewNumber: 0, x1: 10, y1: 0, x2: 10, y2: 30, x3: 0, y3: 15, DimensionOrientation.Horizontal)); + + // Откат: число размеров не изменилось (вырожденный размер удалён). + var after = await _drawing.GetViewLineDimensionCountAsync(0); + Assert.Equal(before, after); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task Coincident_points_throw() + { + var partFile = await BuildAndSaveBoxAsync(); + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await _drawing.CreateStandardViewsAsync(partFile, 1.0, 100, 150); + await Assert.ThrowsAsync(() => _drawing.AddLinearDimensionAsync( + viewNumber: 0, x1: 10, y1: 10, x2: 10, y2: 10, x3: 0, y3: 0, DimensionOrientation.Horizontal)); + } + finally { await _docs.CloseAsync(save: false); } + } + + [Fact] + public async Task No_views_throws() + { + // Чертёж без ассоциативных видов: только системный вид → понятная ошибка. + await _docs.CreateAsync(KompasDocumentType.Drawing); + try + { + await Assert.ThrowsAsync(() => _drawing.AddLinearDimensionAsync( + viewNumber: 0, x1: 0, y1: 0, x2: 40, y2: 0, x3: 20, y3: -10, DimensionOrientation.Horizontal)); + } + finally { await _docs.CloseAsync(save: false); } + } +}