a16ee31498
Разбит на PartModeler.cs (ядро: реестр, helpers, NewParam, reset/dispose), PartModeler.Sketch.cs (эскизы и 2D-примитивы), PartModeler.Features.cs (выдавливание/вращение/скругление/фаска). Поведение не изменено, регрессия ModelingTests 7/7 зелёная. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
207 lines
11 KiB
C#
207 lines
11 KiB
C#
using System.Runtime.Versioning;
|
|
using Kompas6API5;
|
|
using Kompas6Constants3D;
|
|
|
|
namespace Kompas.Mcp.Core.Modeling;
|
|
|
|
/// <summary>PartModeler: формообразующие операции (выдавливание, вращение, скругление, фаска).</summary>
|
|
[SupportedOSPlatform("windows")]
|
|
public sealed partial class PartModeler
|
|
{
|
|
/// <summary>
|
|
/// Выдавить эскиз. <paramref name="cut"/> — вырезание вместо добавления материала.
|
|
/// Возвращает id операции.
|
|
/// </summary>
|
|
/// <param name="throughAll">Сквозное выдавливание/вырез через всю деталь в обе стороны
|
|
/// (для отверстий — гарантированно пробивает материал; <paramref name="depth"/> игнорируется).</param>
|
|
public Task<int> ExtrudeAsync(
|
|
int sketchId, double depth, bool cut = false, bool forward = true,
|
|
double draftAngle = 0, bool throughAll = false, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
if (!throughAll && depth <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(depth), "Глубина должна быть > 0 (или используйте throughAll).");
|
|
var sketch = RequireSketch(sketchId);
|
|
CloseSketchCore(sketch); // эскиз должен быть закрыт перед операцией
|
|
|
|
var part = GetTopPart();
|
|
var type = cut ? Obj3dType.o3d_cutExtrusion : Obj3dType.o3d_bossExtrusion;
|
|
var entity = part.NewEntity((short)type) as ksEntity
|
|
?? throw new InvalidOperationException("NewEntity операции выдавливания вернул null.");
|
|
|
|
// Конкретные типы определений (без dynamic): ошибки связывания — на этапе компиляции.
|
|
if (cut)
|
|
{
|
|
var def = entity.GetDefinition() as ksCutExtrusionDefinition
|
|
?? throw new InvalidOperationException("GetDefinition() выреза вернул не ksCutExtrusionDefinition.");
|
|
if (throughAll)
|
|
{
|
|
def.directionType = (short)Direction_Type.dtBoth;
|
|
def.SetSideParam(true, (short)End_Type.etThroughAll, depth, draftAngle, false);
|
|
def.SetSideParam(false, (short)End_Type.etThroughAll, depth, draftAngle, false);
|
|
}
|
|
else
|
|
{
|
|
def.directionType = (short)Direction_Type.dtNormal;
|
|
def.SetSideParam(forward, (short)End_Type.etBlind, depth, draftAngle, false);
|
|
}
|
|
def.SetSketch(sketch.Entity);
|
|
}
|
|
else
|
|
{
|
|
if (throughAll)
|
|
throw new ArgumentException("throughAll поддерживается только для выреза (cut).", nameof(throughAll));
|
|
var def = entity.GetDefinition() as ksBossExtrusionDefinition
|
|
?? throw new InvalidOperationException("GetDefinition() выдавливания вернул не ksBossExtrusionDefinition.");
|
|
def.directionType = (short)Direction_Type.dtNormal;
|
|
def.SetSideParam(forward, (short)End_Type.etBlind, depth, draftAngle, false);
|
|
def.SetSketch(sketch.Entity);
|
|
}
|
|
|
|
if (!entity.Create())
|
|
throw new InvalidOperationException("Create() операции выдавливания вернул FALSE.");
|
|
|
|
var id = _nextId++;
|
|
_features[id] = entity;
|
|
return id;
|
|
}, ct);
|
|
|
|
/// <summary>
|
|
/// Вращать эскиз вокруг его осевой линии (добавьте её через AddAxis). <paramref name="cut"/> —
|
|
/// вырезание вместо добавления материала. Угол <paramref name="angle"/> — в градусах.
|
|
/// Возвращает id операции.
|
|
/// </summary>
|
|
public Task<int> RevolveAsync(
|
|
int sketchId, double angle = 360, bool cut = false, bool forward = true,
|
|
CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
if (angle is <= 0 or > 360)
|
|
throw new ArgumentOutOfRangeException(nameof(angle), "Угол вращения должен быть в (0; 360].");
|
|
var sketch = RequireSketch(sketchId);
|
|
CloseSketchCore(sketch); // эскиз должен быть закрыт перед операцией
|
|
|
|
var part = GetTopPart();
|
|
var type = cut ? Obj3dType.o3d_cutRotated : Obj3dType.o3d_bossRotated;
|
|
var entity = part.NewEntity((short)type) as ksEntity
|
|
?? throw new InvalidOperationException("NewEntity операции вращения вернул null.");
|
|
|
|
if (cut)
|
|
{
|
|
var def = entity.GetDefinition() as ksCutRotatedDefinition
|
|
?? throw new InvalidOperationException("GetDefinition() выреза вращением вернул не ksCutRotatedDefinition.");
|
|
def.directionType = (short)Direction_Type.dtNormal;
|
|
def.SetThinParam(false, 0, 0, 0);
|
|
def.SetSideParam(forward, angle);
|
|
def.SetSketch(sketch.Entity);
|
|
}
|
|
else
|
|
{
|
|
var def = entity.GetDefinition() as ksBossRotatedDefinition
|
|
?? throw new InvalidOperationException("GetDefinition() вращения вернул не ksBossRotatedDefinition.");
|
|
def.directionType = (short)Direction_Type.dtNormal;
|
|
def.toroidShapeType = false; // сфероид (сплошное тело), не тороид
|
|
def.SetThinParam(false, 0, 0, 0);
|
|
def.SetSideParam(forward, angle);
|
|
def.SetSketch(sketch.Entity);
|
|
}
|
|
|
|
if (!entity.Create())
|
|
throw new InvalidOperationException("Create() операции вращения вернул FALSE.");
|
|
|
|
var id = _nextId++;
|
|
_features[id] = entity;
|
|
return id;
|
|
}, ct);
|
|
|
|
/// <summary>
|
|
/// Скруглить ребро детали, найденное по точке (мировые координаты, мм), радиусом <paramref name="radius"/>.
|
|
/// Возвращает id операции.
|
|
/// </summary>
|
|
public Task<int> FilletEdgeAsync(double x, double y, double z, double radius, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
var part = GetTopPart();
|
|
return CreateFillet(part, SelectEdgeByPoint(part, x, y, z), radius);
|
|
}, ct);
|
|
|
|
/// <summary>Скруглить ребро детали по индексу из list_edges радиусом <paramref name="radius"/>.</summary>
|
|
public Task<int> FilletEdgeIndexAsync(int edgeIndex, double radius, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
var part = GetTopPart();
|
|
return CreateFillet(part, SelectEdgeByIndex(part, edgeIndex), radius);
|
|
}, ct);
|
|
|
|
private int CreateFillet(ksPart part, ksEntity edge, double radius)
|
|
{
|
|
if (radius <= 0) throw new ArgumentOutOfRangeException(nameof(radius), "Радиус скругления должен быть > 0.");
|
|
|
|
var entity = part.NewEntity((short)Obj3dType.o3d_fillet) as ksEntity
|
|
?? throw new InvalidOperationException("NewEntity(o3d_fillet) вернул null.");
|
|
var def = entity.GetDefinition() as ksFilletDefinition
|
|
?? throw new InvalidOperationException("GetDefinition() скругления вернул не ksFilletDefinition.");
|
|
def.radius = radius;
|
|
def.tangent = false;
|
|
var arr = def.array() as ksEntityCollection
|
|
?? throw new InvalidOperationException("array() скругления вернул не ksEntityCollection.");
|
|
arr.Add(edge);
|
|
|
|
if (!entity.Create())
|
|
throw new InvalidOperationException("Create() скругления вернул FALSE (радиус слишком велик для ребра?).");
|
|
|
|
var id = _nextId++;
|
|
_features[id] = entity;
|
|
return id;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Снять фаску с ребра детали, найденного по точке (мировые координаты, мм), катетом <paramref name="distance"/>.
|
|
/// Возвращает id операции.
|
|
/// </summary>
|
|
public Task<int> ChamferEdgeAsync(double x, double y, double z, double distance, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
var part = GetTopPart();
|
|
return CreateChamfer(part, SelectEdgeByPoint(part, x, y, z), distance);
|
|
}, ct);
|
|
|
|
/// <summary>Снять фаску с ребра детали по индексу из list_edges катетом <paramref name="distance"/>.</summary>
|
|
public Task<int> ChamferEdgeIndexAsync(int edgeIndex, double distance, CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
var part = GetTopPart();
|
|
return CreateChamfer(part, SelectEdgeByIndex(part, edgeIndex), distance);
|
|
}, ct);
|
|
|
|
private int CreateChamfer(ksPart part, ksEntity edge, double distance)
|
|
{
|
|
if (distance <= 0) throw new ArgumentOutOfRangeException(nameof(distance), "Катет фаски должен быть > 0.");
|
|
|
|
var entity = part.NewEntity((short)Obj3dType.o3d_chamfer) as ksEntity
|
|
?? throw new InvalidOperationException("NewEntity(o3d_chamfer) вернул null.");
|
|
var def = entity.GetDefinition() as ksChamferDefinition
|
|
?? throw new InvalidOperationException("GetDefinition() фаски вернул не ksChamferDefinition.");
|
|
def.tangent = false;
|
|
def.SetChamferParam(true, distance, distance);
|
|
var arr = def.array() as ksEntityCollection
|
|
?? throw new InvalidOperationException("array() фаски вернул не ksEntityCollection.");
|
|
arr.Add(edge);
|
|
|
|
if (!entity.Create())
|
|
throw new InvalidOperationException("Create() фаски вернул FALSE (катет слишком велик для ребра?).");
|
|
|
|
var id = _nextId++;
|
|
_features[id] = entity;
|
|
return id;
|
|
}
|
|
|
|
/// <summary>Перестроить документ.</summary>
|
|
public Task RebuildAsync(CancellationToken ct = default)
|
|
=> _dispatcher.InvokeAsync(() =>
|
|
{
|
|
var doc3d = ActiveDoc3D();
|
|
doc3d.RebuildDocument();
|
|
}, ct);
|
|
}
|