M7a: surface bodies — SurfaceExtrude + SurfaceRevolve (open shells)

Two body-producing features that emit an open shell instead of a capped
solid: SurfaceExtrude (prism of a sketch wire, no end caps) and
SurfaceRevolve (revolve of a wire about an in-plane axis, no caps). Each
appends a new sheet body whose TopoDS_Shape has no TopAbs_SOLID.

Purely additive: two enum values appended at the end of CadFeatureType,
reusing existing serialized fields (sketch_ref/distance,
revolve_angle/revolve_axis). No new cereal fields, recipe stays v2, golden
fixture unchanged. is_sheet_shape() derives sheet-ness from the OCCT shape
type (bodies are not serialized). MCP surface_extrude/surface_revolve added
as pure additions. Suite 99 cases / 1474 assertions green.

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 09:19:02 +02:00
co-authored by Claude Opus 4.8
parent 15ea0a813f
commit 01e474e17c
4 changed files with 213 additions and 1 deletions
+69
View File
@@ -25,6 +25,8 @@
#include <TopTools_ListOfShape.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeCone.hxx>
#include <BRepPrimAPI_MakePrism.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepLib.hxx>
#include <BRepAdaptor_Curve.hxx>
@@ -36,10 +38,12 @@
#include <GCE2d_MakeSegment.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopAbs.hxx>
#include <TopoDS_Shell.hxx>
#include <TopoDS_Compound.hxx> // multi-body: compound of bodies for display/compat
#include <BRep_Builder.hxx>
#include <TopAbs_Orientation.hxx> // outward-normal orientation for face-extrude
#include <TopExp_Explorer.hxx> // is_sheet_shape
#include <gp_Circ.hxx>
#include <gp_Ax2.hxx>
#include <gp_Ax3.hxx>
@@ -2012,6 +2016,39 @@ void CadDocument::apply_feature(TopoDS_Shape& result, bool& have_body,
}
break;
}
case CadFeatureType::SurfaceExtrude: {
if (f.sketch_ref < 0 || f.sketch_ref >= int(features.size()))
throw std::runtime_error("surface-extrude: bad sketch ref");
const CadFeature& sk = features[f.sketch_ref];
if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project)
throw std::runtime_error("surface-extrude: ref is not a sketch");
TopoDS_Wire wire = build_sketch_wire(sk);
if (wire.IsNull()) throw std::runtime_error("surface-extrude: empty profile");
gp_Dir nrm(sk.plane.normal.x(), sk.plane.normal.y(), sk.plane.normal.z());
gp_Vec v(nrm.XYZ() * f.distance);
TopoDS_Shape shell = BRepPrimAPI_MakePrism(wire, v, false, true).Shape();
if (shell.IsNull()) throw std::runtime_error("surface-extrude: prism failed");
result = shell; have_body = true;
break;
}
case CadFeatureType::SurfaceRevolve: {
if (f.sketch_ref < 0 || f.sketch_ref >= int(features.size()))
throw std::runtime_error("surface-revolve: bad sketch ref");
const CadFeature& sk = features[f.sketch_ref];
if (sk.type != CadFeatureType::Sketch && sk.type != CadFeatureType::Project)
throw std::runtime_error("surface-revolve: ref is not a sketch");
TopoDS_Wire wire = build_sketch_wire(sk);
if (wire.IsNull()) throw std::runtime_error("surface-revolve: empty profile");
const Vec3d& adir = (f.revolve_axis == 1) ? sk.plane.y_axis : sk.plane.x_axis;
gp_Pnt o(sk.plane.origin.x(), sk.plane.origin.y(), sk.plane.origin.z());
gp_Dir xd(adir.x(), adir.y(), adir.z());
gp_Ax1 axis(o, xd);
const double ang = f.revolve_angle * M_PI / 180.0;
BRepPrimAPI_MakeRevol rev(wire, axis, ang, false);
if (!rev.IsDone()) throw std::runtime_error("surface-revolve: revolve failed");
result = rev.Shape(); have_body = true;
break;
}
case CadFeatureType::Sweep: {
const CadFeature& sk = (f.sketch_ref >= 0 && f.sketch_ref < int(features.size())
&& (features[f.sketch_ref].type == CadFeatureType::Sketch
@@ -2741,6 +2778,7 @@ void CadDocument::route_feature(std::vector<CadBody>& bodies, const CadFeature&
// mutates the target body in place.
const bool starts_new = bodies.empty()
|| f.type == CadFeatureType::Import // an imported solid is always its own base body
|| f.type == CadFeatureType::SurfaceExtrude || f.type == CadFeatureType::SurfaceRevolve
|| ((f.type == CadFeatureType::Extrude || f.type == CadFeatureType::Revolve
|| f.type == CadFeatureType::Sweep || f.type == CadFeatureType::Loft)
&& f.mode == BooleanMode::New);
@@ -2973,4 +3011,35 @@ GeometryEngine::MassProps CadDocument::body_mass_properties(int body_index) cons
return GeometryEngine::mass_properties(bodies[body_index].shape);
}
int CadDocument::add_surface_extrude(int sketch_ref, double distance, const std::string& name)
{
CadFeature f;
f.type = CadFeatureType::SurfaceExtrude;
f.name = name;
f.sketch_ref = sketch_ref;
f.distance = distance;
f.mode = BooleanMode::New;
features.push_back(f);
return int(features.size()) - 1;
}
int CadDocument::add_surface_revolve(int sketch_ref, double angle_deg, int axis, const std::string& name)
{
CadFeature f;
f.type = CadFeatureType::SurfaceRevolve;
f.name = name;
f.sketch_ref = sketch_ref;
f.revolve_angle = angle_deg;
f.revolve_axis = axis;
f.mode = BooleanMode::New;
features.push_back(f);
return int(features.size()) - 1;
}
// ponytail: derived from the OCCT shape type; no stored flag, bodies aren't serialized anyway.
bool CadDocument::is_sheet_shape(const TopoDS_Shape& s)
{
return !TopExp_Explorer(s, TopAbs_SOLID).More();
}
} // namespace Slic3r
+6 -1
View File
@@ -19,7 +19,7 @@
namespace Slic3r {
enum class CadFeatureType { Sketch, Extrude, Fillet, Chamfer, Hole, Thread, Shell, Revolve, Sweep, Pattern, Plane, Loft, Draft, Import, Boolean, Cut, Mirror, Axis, CoordSys, Helix, Transform, Thicken, Project, DeleteFace, Rib };
enum class CadFeatureType { Sketch, Extrude, Fillet, Chamfer, Hole, Thread, Shell, Revolve, Sweep, Pattern, Plane, Loft, Draft, Import, Boolean, Cut, Mirror, Axis, CoordSys, Helix, Transform, Thicken, Project, DeleteFace, Rib, SurfaceExtrude, SurfaceRevolve };
enum class SketchShape { Rectangle, Circle };
enum class PlaneType { Offset, Angle, Midplane, Tangent, TwoEdges, Coincident };
enum class AxisType { TwoPoints, FaceNormal, CylinderCenterline, PlaneIntersection, AlongEdge };
@@ -510,6 +510,8 @@ public:
int add_thicken(int target_body, int face, double thickness, bool flip, const std::string& name);
int add_delete_face(int target_body, const std::vector<int>& faces,
const std::string& name);
int add_surface_extrude(int sketch_ref, double distance, const std::string& name);
int add_surface_revolve(int sketch_ref, double angle_deg, int axis, const std::string& name);
// Datum plane: derived from base (0=XY/1=XZ/2=YZ/3+N=Nth earlier datum), offset
// along its normal, optional tilt about a base axis. Produces no solid.
int add_plane(int base, double offset, double angle_tilt, int axis,
@@ -553,6 +555,9 @@ public:
GeometryEngine::MassProps body_mass_properties(int body_index) const;
// ponytail: derived from the OCCT shape type; no stored flag, bodies aren't serialized anyway.
static bool is_sheet_shape(const TopoDS_Shape& s); // true if TopExp finds no TopAbs_SOLID
// Undo/redo of the feature recipe (Onshape-style Ctrl+Z). The caller marks a
// user-action boundary by calling checkpoint() BEFORE the mutation(s) for that
// action (add/delete/move/replace, or a direct features edit). undo()/redo() then