mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 18:31:11 +00:00
CAD: Thicken feature — offset a face into a thin solid plate
Pick a face of an existing body, offset it by a wall thickness along its normal, and append the resulting thin solid as a new body. Onshape-parity Tier-2 item; the kernel had Shell (hollow a whole solid) but no way to turn a single face into a plate. Kernel: CadFeatureType::Thicken, add_thicken()/apply_thicken() as a body-level op next to Transform/Mirror. The picked face is wrapped in a TopoDS_Shell and offset via BRepOffsetAPI_MakeThickSolid::MakeThickSolidBySimple; the result is orientation-normalised to positive volume (same convention as apply_mirror). Serialization stays append-only — thicken_face, thicken_thickness, thicken_flip appended to save/load, recipe version unchanged at 2. MCP: `thicken` method (body/face/thickness/flip) plus the missing feature_type_name() case. Tests: 6 new [CadDocument] cases (plate volume within 1%, flip direction, bad face id, zero thickness, fuse-with-source, round-trip). Golden fixture regenerated with a GoldenThicken feature and exact field-value assertions. Suite 57 -> 63 cases, 1054 -> 1119 assertions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0100c95ed1
commit
9da5851534
@@ -31,6 +31,7 @@
|
||||
#include <GCE2d_MakeSegment.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.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
|
||||
@@ -753,6 +754,20 @@ int CadDocument::add_transform(int target_body, const Vec3d& translate, const Ve
|
||||
return int(features.size()) - 1;
|
||||
}
|
||||
|
||||
int CadDocument::add_thicken(int target_body, int face, double thickness, bool flip,
|
||||
const std::string& name)
|
||||
{
|
||||
CadFeature f;
|
||||
f.type = CadFeatureType::Thicken;
|
||||
f.name = name;
|
||||
f.target_body = target_body;
|
||||
f.thicken_face = face;
|
||||
f.thicken_thickness = thickness;
|
||||
f.thicken_flip = flip;
|
||||
features.push_back(f);
|
||||
return int(features.size()) - 1;
|
||||
}
|
||||
|
||||
int CadDocument::add_plane(int base, double offset, double angle_tilt, int axis,
|
||||
const std::string& name)
|
||||
{
|
||||
@@ -2018,6 +2033,41 @@ void CadDocument::apply_transform(std::vector<CadBody>& bodies, const CadFeature
|
||||
bodies[tgt].shape = moved;
|
||||
}
|
||||
|
||||
void CadDocument::apply_thicken(std::vector<CadBody>& bodies, const CadFeature& f) const
|
||||
{
|
||||
const int nb = int(bodies.size());
|
||||
if (nb == 0) throw std::runtime_error("thicken: no target body");
|
||||
const int tgt = (f.target_body >= 0 && f.target_body < nb) ? f.target_body : nb - 1;
|
||||
if (tgt < 0 || bodies[tgt].shape.IsNull()) throw std::runtime_error("thicken: no target body");
|
||||
|
||||
TopoDS_Face fc = GeometryEngine::face_by_index(bodies[tgt].shape, f.thicken_face);
|
||||
if (fc.IsNull()) throw std::runtime_error("thicken: face not found");
|
||||
if (std::abs(f.thicken_thickness) < 1e-9) throw std::runtime_error("thicken: thickness is zero");
|
||||
|
||||
TopoDS_Shell shell;
|
||||
BRep_Builder bb;
|
||||
bb.MakeShell(shell);
|
||||
bb.Add(shell, fc);
|
||||
|
||||
const double off = f.thicken_flip ? -std::abs(f.thicken_thickness)
|
||||
: std::abs(f.thicken_thickness);
|
||||
BRepOffsetAPI_MakeThickSolid mts;
|
||||
mts.MakeThickSolidBySimple(shell, off);
|
||||
mts.Build();
|
||||
if (!mts.IsDone()) throw std::runtime_error("thicken: failed");
|
||||
TopoDS_Shape solid = mts.Shape();
|
||||
if (solid.IsNull()) throw std::runtime_error("thicken: produced no geometry");
|
||||
|
||||
// MakeThickSolidBySimple may produce a reversed solid. Ensure positive volume.
|
||||
{
|
||||
GProp_GProps props;
|
||||
BRepGProp::VolumeProperties(solid, props);
|
||||
if (props.Mass() < 0.0) solid.Reverse();
|
||||
}
|
||||
|
||||
bodies.push_back({solid, f.name.empty() ? std::string("Thicken") : f.name});
|
||||
}
|
||||
|
||||
void CadDocument::route_feature(std::vector<CadBody>& bodies, const CadFeature& f) const
|
||||
{
|
||||
if (f.type == CadFeatureType::Plane) return; // datum plane: not part of the body pipeline
|
||||
@@ -2028,6 +2078,7 @@ void CadDocument::route_feature(std::vector<CadBody>& bodies, const CadFeature&
|
||||
if (f.type == CadFeatureType::Cut) { apply_cut(bodies, f); return; } // plane-split body
|
||||
if (f.type == CadFeatureType::Mirror) { apply_mirror(bodies, f); return; } // mirror body about plane
|
||||
if (f.type == CadFeatureType::Transform) { apply_transform(bodies, f); return; } // move/rotate body
|
||||
if (f.type == CadFeatureType::Thicken) { apply_thicken(bodies, f); return; } // face -> plate
|
||||
// Resolve the target body: explicit target_body when valid, else the last body.
|
||||
const int t = (f.target_body >= 0 && f.target_body < int(bodies.size()))
|
||||
? f.target_body : int(bodies.size()) - 1;
|
||||
|
||||
@@ -17,7 +17,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 };
|
||||
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 };
|
||||
enum class SketchShape { Rectangle, Circle };
|
||||
enum class PlaneType { Offset, Angle, Midplane, Tangent, TwoEdges, Coincident };
|
||||
enum class AxisType { TwoPoints, FaceNormal, CylinderCenterline, PlaneIntersection, AlongEdge };
|
||||
@@ -246,6 +246,12 @@ struct CadFeature {
|
||||
double xf_angle_deg{0};
|
||||
bool xf_copy{false}; // true: keep the original, append the moved copy as a new body
|
||||
|
||||
// Thicken feature: offset one face of an existing body into a new thin solid body.
|
||||
// The face belongs to `target_body`; the offset runs along the face normal.
|
||||
int thicken_face{-1}; // global face id on the target body; -1 = invalid
|
||||
double thicken_thickness{2}; // wall thickness (always used as |value|)
|
||||
bool thicken_flip{false}; // true: offset against the face normal
|
||||
|
||||
template<class Archive>
|
||||
void save(Archive& ar) const {
|
||||
std::string brep = (type == CadFeatureType::Import) ? brep_to_string(imported_solid) : std::string();
|
||||
@@ -272,7 +278,8 @@ struct CadFeature {
|
||||
axis_type, axis_p1, axis_p2, axis_body, axis_face, axis_edge, axis_plane_a, axis_plane_b,
|
||||
coordsys_type, coordsys_point, coordsys_body, coordsys_face, coordsys_edge, coordsys_x_hint,
|
||||
helix_radius, helix_pitch, helix_height, helix_left_handed, helix_taper_deg,
|
||||
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy);
|
||||
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy,
|
||||
thicken_face, thicken_thickness, thicken_flip);
|
||||
}
|
||||
template<class Archive>
|
||||
void load(Archive& ar) {
|
||||
@@ -300,7 +307,8 @@ struct CadFeature {
|
||||
axis_type, axis_p1, axis_p2, axis_body, axis_face, axis_edge, axis_plane_a, axis_plane_b,
|
||||
coordsys_type, coordsys_point, coordsys_body, coordsys_face, coordsys_edge, coordsys_x_hint,
|
||||
helix_radius, helix_pitch, helix_height, helix_left_handed, helix_taper_deg,
|
||||
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy);
|
||||
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy,
|
||||
thicken_face, thicken_thickness, thicken_flip);
|
||||
imported_solid = brep_from_string(brep);
|
||||
}
|
||||
};
|
||||
@@ -406,6 +414,9 @@ public:
|
||||
// copy=true keeps the source body and appends the transformed one as a new body.
|
||||
int add_transform(int target_body, const Vec3d& translate, const Vec3d& axis,
|
||||
const Vec3d& pivot, double angle_deg, bool copy, const std::string& name);
|
||||
// Offset face `face` of `target_body` by `thickness` along its normal, producing a new
|
||||
// thin solid appended as a new body. flip=true offsets against the normal.
|
||||
int add_thicken(int target_body, int face, double thickness, bool flip, 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,
|
||||
@@ -513,6 +524,7 @@ private:
|
||||
void apply_cut(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
||||
void apply_mirror(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
||||
void apply_transform(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
||||
void apply_thicken(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
||||
|
||||
// Undo/redo stacks of feature-list snapshots. checkpoint() pushes onto m_undo and
|
||||
// clears m_redo; undo()/redo() shuffle the current state between them. Capped so a
|
||||
|
||||
@@ -75,6 +75,7 @@ const char* feature_type_name(CadFeatureType t)
|
||||
case CadFeatureType::CoordSys: return "CoordSys";
|
||||
case CadFeatureType::Helix: return "Helix";
|
||||
case CadFeatureType::Transform: return "Transform";
|
||||
case CadFeatureType::Thicken: return "Thicken";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
@@ -232,6 +233,13 @@ json describe_tools()
|
||||
json{{"name", "taper_deg"}, {"type", "number"}, {"unit", "deg"}, {"default", 0}},
|
||||
json{{"name", "plane"}, {"type", "string"}, {"enum", json::array({"XY", "XZ", "YZ"})}, {"default", "XY"}},
|
||||
})}},
|
||||
json{{"name", "thicken"}, {"summary", "Offset a face of a body by a wall thickness, producing a new thin solid body."},
|
||||
{"params", json::array({
|
||||
json{{"name", "body"}, {"type", "integer"}, {"default", -1}, {"description", "target body; omit for the last body"}},
|
||||
json{{"name", "face"}, {"type", "integer"}, {"description", "face id to thicken (query_topology)"}},
|
||||
json{{"name", "thickness"}, {"type", "number"}, {"unit", "mm"}, {"default", 2}, {"min", 0.01}},
|
||||
json{{"name", "flip"}, {"type", "boolean"}, {"default", false}},
|
||||
})}},
|
||||
json{{"name", "query_topology"}, {"summary", "Measured faces (centroid/normal/cylinder) and edges (length/circle) of a body."},
|
||||
{"params", json::array({
|
||||
json{{"name", "body"}, {"type", "integer"}, {"default", 0}},
|
||||
@@ -881,6 +889,23 @@ json action_transform(DesignPanel* panel, const json& params)
|
||||
return json{{"ok", ok}, {"transform_index", idx}, {"bodies", int(doc.bodies.size())}, {"error", doc.error}};
|
||||
}
|
||||
|
||||
json action_thicken(DesignPanel* panel, const json& params)
|
||||
{
|
||||
if (!params.contains("face")) throw std::runtime_error("thicken needs 'face' (id from query_topology)");
|
||||
CadDocument& doc = panel->mcp_doc();
|
||||
if (doc.bodies.empty()) throw std::runtime_error("no body to thicken");
|
||||
int bi = target_body_arg(params, doc);
|
||||
int face = params["face"].get<int>();
|
||||
double thickness = params.value("thickness", 2.0);
|
||||
bool flip = params.value("flip", false);
|
||||
doc.checkpoint();
|
||||
int idx = doc.add_thicken(bi, face, thickness, flip, "Thicken");
|
||||
bool ok = doc.recompute();
|
||||
if (!ok) doc.undo();
|
||||
panel->mcp_after_change();
|
||||
return json{{"ok", ok}, {"thicken_index", idx}, {"bodies", int(doc.bodies.size())}, {"error", doc.error}};
|
||||
}
|
||||
|
||||
json action_axis(DesignPanel* panel, const json& params)
|
||||
{
|
||||
CadDocument& doc = panel->mcp_doc();
|
||||
@@ -980,6 +1005,7 @@ std::string handle_on_main(const std::string& method, const json& params, const
|
||||
if (method == "draft") return rpc_result(id, action_draft(panel, params));
|
||||
if (method == "mirror") return rpc_result(id, action_mirror(panel, params));
|
||||
if (method == "transform") return rpc_result(id, action_transform(panel, params));
|
||||
if (method == "thicken") return rpc_result(id, action_thicken(panel, params));
|
||||
if (method == "axis") return rpc_result(id, action_axis(panel, params));
|
||||
if (method == "coordsys") return rpc_result(id, action_coordsys(panel, params));
|
||||
if (method == "helix") return rpc_result(id, action_helix(panel, params));
|
||||
|
||||
Reference in New Issue
Block a user