M5b: rib — thin stiffening wall grown from an open sketch line

New CadFeatureType::Rib (appended). A straight open Line entity in a sketch
is offset ±thickness/2 along its in-plane perpendicular into a thin rectangle,
extruded rib_depth along the sketch-plane normal, and fused to the target body.
Line-only for now (ponytail; polyline/arc ribs are a later extension) — a
non-line entity fails cleanly at recompute. Four serialized fields
(rib_sketch_ref/rib_entity/rib_thickness/rib_depth) appended to both symmetric
cereal lists (version stays 2, golden fixture regenerated 29525->30269). MCP:
rib. 3 new [CadDocument][rib] tests; suite 86/1376.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
Tommaso Bianchi
2026-07-25 04:03:52 +02:00
co-authored by Claude Opus 4.8
parent 0bbf22ceae
commit a606dfe00a
5 changed files with 200 additions and 5 deletions
+47
View File
@@ -748,6 +748,21 @@ int CadDocument::add_shell(double thickness, int face, int target_body, const st
return int(features.size()) - 1;
}
int CadDocument::add_rib(int sketch_ref, int entity, double thickness, double depth,
int target_body, const std::string& name)
{
CadFeature f;
f.type = CadFeatureType::Rib;
f.name = name;
f.rib_sketch_ref = sketch_ref;
f.rib_entity = entity;
f.rib_thickness = thickness;
f.rib_depth = depth;
f.target_body = target_body;
features.push_back(f);
return int(features.size()) - 1;
}
int CadDocument::add_delete_face(int target_body, const std::vector<int>& faces,
const std::string& name)
{
@@ -1784,6 +1799,38 @@ void CadDocument::apply_feature(TopoDS_Shape& result, bool& have_body,
}
break;
}
case CadFeatureType::Rib: {
if (!have_body) throw std::runtime_error("rib needs a body");
if (f.rib_sketch_ref < 0 || f.rib_sketch_ref >= (int)features.size())
throw std::runtime_error("rib: bad sketch ref");
const CadFeature& sk = features[f.rib_sketch_ref];
if (sk.type != CadFeatureType::Sketch)
throw std::runtime_error("rib: ref is not a sketch");
if (f.rib_entity < 0 || f.rib_entity >= (int)sk.entities.size())
throw std::runtime_error("rib: bad entity");
const SketchEntity& ln = sk.entities[f.rib_entity];
if (ln.type != SketchEntity::Type::Line)
throw std::runtime_error("rib: entity must be a line"); // ponytail: line-only for now
// Thin rectangle centred on the line, in the sketch plane: offset both endpoints by
// +/-thickness/2 along the in-plane perpendicular of the line direction.
const SketchPlane& pl = sk.plane;
Vec2d a = ln.p0, b = ln.p1;
Vec2d dir = (b - a); double L = dir.norm();
if (L < 1e-9) throw std::runtime_error("rib: degenerate line");
dir /= L;
Vec2d perp(-dir.y(), dir.x());
double h = f.rib_thickness * 0.5;
Vec2d q0 = a + perp*h, q1 = b + perp*h, q2 = b - perp*h, q3 = a - perp*h;
auto w3 = [&](const Vec2d& p){ Vec3d w = pl.to_world(p); return gp_Pnt(w.x(),w.y(),w.z()); };
BRepBuilderAPI_MakePolygon poly(w3(q0), w3(q1), w3(q2), w3(q3), Standard_True);
if (!poly.IsDone()) throw std::runtime_error("rib: profile failed");
TopoDS_Shape wall = SketchEngine::make_extrude(poly.Wire(), pl, f.rib_depth, false, 0.0);
if (wall.IsNull()) throw std::runtime_error("rib: extrude failed");
BRepAlgoAPI_Fuse fuse(result, wall);
if (!fuse.IsDone()) throw std::runtime_error("rib: fuse failed");
result = fuse.Shape();
break;
}
case CadFeatureType::Fillet:
if (!have_body) throw std::runtime_error("fillet needs a body");
if (f.dressup_edge >= 0)