feat(feature): операция Ребро жёсткости (rib)

RibAsync (o3d_ribOperation): SetSketch + index/angle/side + SetThinParam.
SketchGeometry.RibSide маппинг (left/right/up/down). Инструмент rib в FeatureTools.
Эмпирически: контур ребра висит в зазоре (не касается тела), side=left для косынки.
Integration: L-уголок + ребро в углу → объём растёт.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 22:51:58 +03:00
parent 7a9964b6f1
commit fc99739e1e
6 changed files with 171 additions and 0 deletions
@@ -48,4 +48,37 @@ public sealed class FeatureOpsTests
}
finally { await _docs.CloseAsync(save: false); }
}
[Fact]
public async Task Rib_adds_material_in_corner_of_L_bracket()
{
await _docs.CreateAsync(KompasDocumentType.Part);
try
{
// Г-образный уголок: L-профиль на XOZ (замкнутая ломаная), выдавить по Y на 30.
// Площадь L = 40×5 + 5×35 = 375 → объём 375·30 = 11250.
var sBody = await _modeler.OpenSketchAsync(BasePlane.XOZ);
var lprofile = new (double x, double y)[] { (0, 0), (40, 0), (40, 5), (5, 5), (5, 40), (0, 40) };
await _modeler.AddPolylineAsync(sBody, lprofile, closed: true);
await _modeler.CloseSketchAsync(sBody);
await _modeler.ExtrudeAsync(sBody, depth: 30);
var before = (await _query.GetPartInfoAsync()).Volume;
Assert.True(before > 0);
// Ребро-косынка: разомкнутый отрезок-диагональ во внутреннем углу, на XOZ (Y=0).
// side=up (ортогонально, +Y), несимметрично → растёт от Y=0 вглубь тела на 5 мм.
var sRib = await _modeler.OpenSketchAsync(BasePlane.XOZ);
await _modeler.AddLineAsync(sRib, 25, 10, 10, 25); // висит в пустом углу, не касаясь полок
await _modeler.CloseSketchAsync(sRib);
var id = await _modeler.RibAsync(sRib, thickness: 5, side: "left", symmetric: false);
Assert.True(id > 0);
await _modeler.RebuildAsync();
// Косынка добавляет материал: объём растёт, но в разумных пределах.
var after = (await _query.GetPartInfoAsync()).Volume;
Assert.True(after > before, "После ребра жёсткости объём должен увеличиться.");
Assert.InRange(after, before, before * 1.3);
}
finally { await _docs.CloseAsync(save: false); }
}
}
@@ -17,6 +17,19 @@ public sealed class SketchGeometryTests
public void PolygonDescribe_inverts_inscribed(bool inscribed, bool expected)
=> Assert.Equal(expected, SketchGeometry.PolygonDescribe(inscribed));
[Theory]
[InlineData("left", 0)]
[InlineData("right", 1)]
[InlineData("up", 2)]
[InlineData("down", 3)]
[InlineData("UP", 2)]
public void RibSide_maps_known(string side, int expected)
=> Assert.Equal(expected, (int)SketchGeometry.RibSide(side));
[Fact]
public void RibSide_throws_on_unknown()
=> Assert.Throws<ArgumentException>(() => SketchGeometry.RibSide("diagonal"));
[Fact]
public void RequirePositive_throws_on_zero_or_negative()
{