using System.Runtime.Versioning; using Kompas6API5; using Kompas6Constants; using Kompas6Constants3D; namespace Kompas.Mcp.Core.Modeling; /// PartModeler: создание эскизов и добавление 2D-примитивов. [SupportedOSPlatform("windows")] public sealed partial class PartModeler { /// Создать эскиз на базовой плоскости и открыть его на редактирование. Возвращает id эскиза. public Task OpenSketchAsync(BasePlane plane, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var part = GetTopPart(); var planeEntity = part.GetDefaultEntity((short)BasePlanes.ToObj3dType(plane)) as ksEntity ?? throw new InvalidOperationException("Не удалось получить базовую плоскость."); return CreateSketchOn(part, planeEntity); }, ct); /// /// Создать эскиз на плоской грани детали, найденной по точке ,, /// (мировые координаты модели, мм), и открыть его на редактирование. Возвращает id эскиза. /// Это ключ к итеративному циклу «построил → выбрал грань → новый эскиз на ней». /// public Task OpenSketchOnFaceAsync(double x, double y, double z, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var part = GetTopPart(); var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection ?? throw new InvalidOperationException("Не удалось получить коллекцию граней."); // Оставляем в коллекции только грани, проходящие через указанную точку. faces.SelectByPoint(x, y, z); if (faces.GetCount() == 0) throw new InvalidOperationException( $"В точке ({x}, {y}, {z}) не найдено грани. Сделайте снимок и сверьте координаты."); var face = faces.GetByIndex(0) as ksEntity ?? throw new InvalidOperationException("Грань из коллекции не приводится к ksEntity."); return CreateSketchOn(part, face); }, ct); /// /// Создать эскиз на грани детали, заданной индексом из list_faces, и открыть на редактирование. /// Индекс стабилен, пока геометрия не меняется. Возвращает id эскиза. /// public Task OpenSketchOnFaceIndexAsync(int faceIndex, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var part = GetTopPart(); var faces = part.EntityCollection((short)Obj3dType.o3d_face) as ksEntityCollection ?? throw new InvalidOperationException("Не удалось получить коллекцию граней."); if (faceIndex < 0 || faceIndex >= faces.GetCount()) throw new ArgumentOutOfRangeException(nameof(faceIndex), $"Индекс грани вне диапазона [0; {faces.GetCount() - 1}]. Сверьтесь с list_faces."); var face = faces.GetByIndex(faceIndex) as ksEntity ?? throw new InvalidOperationException("Грань по индексу не приводится к ksEntity."); return CreateSketchOn(part, face); }, ct); /// Общее ядро: создать эскиз на заданной плоскости/грани и открыть на редактирование. private int CreateSketchOn(ksPart part, ksEntity planeEntity) { var entity = part.NewEntity((short)Obj3dType.o3d_sketch) as ksEntity ?? throw new InvalidOperationException("NewEntity(o3d_sketch) вернул null."); var def = entity.GetDefinition() as ksSketchDefinition ?? throw new InvalidOperationException("GetDefinition() эскиза вернул не ksSketchDefinition."); def.SetPlane(planeEntity); entity.Create(); var editor = def.BeginEdit() as ksDocument2D ?? throw new InvalidOperationException("BeginEdit() не вернул ksDocument2D."); var id = _nextId++; _sketches[id] = new SketchEntry { Entity = entity, Definition = def, Editor = editor }; return id; } /// Добавить отрезок в открытый эскиз. public Task AddLineAsync(int sketchId, double x1, double y1, double x2, double y2, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var editor = RequireOpenSketch(sketchId).Editor!; if (editor.ksLineSeg(x1, y1, x2, y2, MainLineStyle) == 0) throw new InvalidOperationException("ksLineSeg вернул 0 (отрезок не создан)."); }, ct); /// Добавить окружность в открытый эскиз. public Task AddCircleAsync(int sketchId, double centerX, double centerY, double radius, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { if (radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius), "Радиус должен быть > 0."); var editor = RequireOpenSketch(sketchId).Editor!; if (editor.ksCircle(centerX, centerY, radius, MainLineStyle) == 0) throw new InvalidOperationException("ksCircle вернул 0 (окружность не создана)."); }, ct); /// Добавить прямоугольник (4 отрезка) по двум углам. public Task AddRectangleAsync(int sketchId, double x1, double y1, double x2, double y2, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var editor = RequireOpenSketch(sketchId).Editor!; void Seg(double ax, double ay, double bx, double by) { if (editor.ksLineSeg(ax, ay, bx, by, MainLineStyle) == 0) throw new InvalidOperationException("ksLineSeg вернул 0 (сторона прямоугольника не создана)."); } Seg(x1, y1, x2, y1); Seg(x2, y1, x2, y2); Seg(x2, y2, x1, y2); Seg(x1, y2, x1, y1); }, ct); /// Добавить осевую линию (стиль «осевая») в эскиз — ось для операции вращения. public Task AddAxisAsync(int sketchId, double x1, double y1, double x2, double y2, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var editor = RequireOpenSketch(sketchId).Editor!; if (editor.ksLineSeg(x1, y1, x2, y2, AxisLineStyle) == 0) throw new InvalidOperationException("ksLineSeg (ось) вернул 0 (ось не создана)."); }, ct); /// Завершить редактирование эскиза. public Task CloseSketchAsync(int sketchId, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => CloseSketchCore(RequireSketch(sketchId)), ct); // ---- Новые примитивы (пакет A) ----------------------------------------- private const short SplineOrder = 4; // порядок NURBS (степень+1); 4 = кубический сплайн private const int PointStyle = 0; // системный стиль точки (см. SDK pstyles) /// Добавить дугу по трём точкам (начало, точка на дуге, конец) в открытый эскиз. public Task AddArc3PointsAsync(int sketchId, double x1, double y1, double x2, double y2, double x3, double y3, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var editor = RequireOpenSketch(sketchId).Editor!; if (editor.ksArcBy3Points(x1, y1, x2, y2, x3, y3, MainLineStyle) == 0) throw new InvalidOperationException("ksArcBy3Points вернул 0 (дуга не создана — три точки коллинеарны?)."); }, ct); /// Добавить дугу по центру, радиусу и углам (градусы) в открытый эскиз. public Task AddArcByAngleAsync(int sketchId, double centerX, double centerY, double radius, double startAngle, double endAngle, bool counterClockwise = true, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { SketchGeometry.RequirePositive(radius, nameof(radius)); var editor = RequireOpenSketch(sketchId).Editor!; var dir = SketchGeometry.ArcDirection(counterClockwise); if (editor.ksArcByAngle(centerX, centerY, radius, startAngle, endAngle, dir, MainLineStyle) == 0) throw new InvalidOperationException("ksArcByAngle вернул 0 (дуга не создана)."); }, ct); /// Добавить эллипс: центр, полуоси A/B (мм), угол наклона большой оси к X (градусы). public Task AddEllipseAsync(int sketchId, double centerX, double centerY, double semiMajor, double semiMinor, double angle = 0, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { SketchGeometry.RequirePositive(semiMajor, nameof(semiMajor)); SketchGeometry.RequirePositive(semiMinor, nameof(semiMinor)); var editor = RequireOpenSketch(sketchId).Editor!; ksEllipseParam? p = null; try { p = NewParam(StructType2DEnum.ko_EllipseParam); // ВНИМАНИЕ: interop генерирует свойства A/B с ЗАГЛАВНОЙ (не a/b как в SDK-доках). Регистр в C# важен. p.xc = centerX; p.yc = centerY; p.A = semiMajor; p.B = semiMinor; p.angle = angle; p.style = MainLineStyle; if (editor.ksEllipse(p) == 0) throw new InvalidOperationException("ksEllipse вернул 0 (эллипс не создан)."); } finally { ReleaseCom(p); } }, ct); /// Добавить ломаную (цепочку отрезков) по списку точек. closed замыкает последнюю с первой. public Task AddPolylineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { // closed-ломаная из 2 точек не образует площадь — требуем минимум 3. SketchGeometry.RequirePoints(points, closed ? 3 : 2, nameof(points)); var editor = RequireOpenSketch(sketchId).Editor!; for (int i = 0; i + 1 < points.Count; i++) Seg(editor, points[i], points[i + 1]); if (closed) Seg(editor, points[^1], points[0]); static void Seg(ksDocument2D ed, (double x, double y) a, (double x, double y) b) { if (ed.ksLineSeg(a.x, a.y, b.x, b.y, MainLineStyle) == 0) throw new InvalidOperationException("ksLineSeg вернул 0 (сегмент ломаной не создан)."); } }, ct); /// Добавить правильный многоугольник: центр, vertexCount вершин, radius (мм), /// inscribed=true — вершины на окружности (вписанный), false — стороны касаются (описанный), /// angle — поворот первой вершины (градусы). public Task AddPolygonAsync(int sketchId, double centerX, double centerY, int vertexCount, double radius, bool inscribed = true, double angle = 0, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { SketchGeometry.RequireVertexCount(vertexCount); SketchGeometry.RequirePositive(radius, nameof(radius)); var editor = RequireOpenSketch(sketchId).Editor!; ksRegularPolygonParam? p = null; try { p = NewParam(StructType2DEnum.ko_RegularPolygonParam); p.count = vertexCount; p.xc = centerX; p.yc = centerY; p.radius = radius; p.ang = angle; p.describe = SketchGeometry.PolygonDescribe(inscribed); p.style = MainLineStyle; if (editor.ksRegularPolygon(p, 0) == 0) throw new InvalidOperationException("ksRegularPolygon вернул 0 (многоугольник не создан)."); } finally { ReleaseCom(p); } }, ct); /// Добавить сплайн (кубический NURBS, порядок 4) через список точек. closed замыкает кривую. public Task AddSplineAsync(int sketchId, IReadOnlyList<(double x, double y)> points, bool closed = false, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { SketchGeometry.RequirePoints(points, 2, nameof(points)); var editor = RequireOpenSketch(sketchId).Editor!; if (editor.ksNurbs(SplineOrder, closed, MainLineStyle) == 0) throw new InvalidOperationException("ksNurbs вернул 0 (сплайн не открыт)."); // ksNurbs открыл составной объект — ksEndObj ОБЯЗАТЕЛЕН в любом исходе, иначе редактор «застрянет». var completed = false; try { foreach (var (x, y) in points) { ksNurbsPointParam? np = null; try { np = NewParam(StructType2DEnum.ko_NurbsPointParam); np.x = x; np.y = y; np.weight = 1.0; if (editor.ksNurbsPoint(np) == 0) throw new InvalidOperationException("ksNurbsPoint вернул 0 (узел сплайна не добавлен)."); } finally { ReleaseCom(np); } } completed = true; } finally { // Закрываем составной объект всегда; ошибку завершения сообщаем только на успешном пути, // чтобы не подменить исходное исключение из цикла. var end = editor.ksEndObj(); if (completed && end == 0) throw new InvalidOperationException("ksEndObj вернул 0 (сплайн не завершён)."); } }, ct); /// Добавить точку в открытый эскиз (опорная точка для построений). public Task AddPointAsync(int sketchId, double x, double y, CancellationToken ct = default) => _dispatcher.InvokeAsync(() => { var editor = RequireOpenSketch(sketchId).Editor!; if (editor.ksPoint(x, y, PointStyle) == 0) throw new InvalidOperationException("ksPoint вернул 0 (точка не создана)."); }, ct); }