mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
MCP slice 3: import_step + validate_against (close the RE loop)
Add the Build-input and Validate ends of the reverse-engineering loop.
import_step loads a STEP as native B-rep bodies (the reference part to
measure), reusing the GUI Import path (read_step_solids -> Import
features -> recompute). validate_against compares a body to a reference
({step:path} | {body:id}) and reports volume delta %, bbox delta, and
centroid offset via OCCT GProp + Bnd_Box — the RE skill's actual
acceptance metric (the "scarto %"); surface-deviation heat-map is the
upgrade path.
With this the full Understand -> Measure -> Build -> Validate loop is
live: 8 methods (describe_tools/describe_scene/query_topology/measure/
slice_body/import_step/validate_against/extrude). Verified: import a STEP
then validate body vs same STEP = 0.0%% delta / 0.0mm offset.
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
5b46b1ed1f
commit
8db9544a1a
@@ -35,6 +35,12 @@
|
|||||||
#include <TopExp_Explorer.hxx>
|
#include <TopExp_Explorer.hxx>
|
||||||
#include <TopoDS.hxx>
|
#include <TopoDS.hxx>
|
||||||
#include <TopoDS_Edge.hxx>
|
#include <TopoDS_Edge.hxx>
|
||||||
|
#include <TopoDS_Compound.hxx>
|
||||||
|
#include <BRep_Builder.hxx>
|
||||||
|
#include <GProp_GProps.hxx>
|
||||||
|
#include <BRepGProp.hxx>
|
||||||
|
#include <Bnd_Box.hxx>
|
||||||
|
#include <BRepBndLib.hxx>
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@@ -114,6 +120,15 @@ json describe_tools()
|
|||||||
json{{"name", "plane"}, {"type", "string"}, {"enum", json::array({"XY", "XZ", "YZ"})}, {"default", "XY"}},
|
json{{"name", "plane"}, {"type", "string"}, {"enum", json::array({"XY", "XZ", "YZ"})}, {"default", "XY"}},
|
||||||
json{{"name", "offset"}, {"type", "number"}, {"unit", "mm"}, {"default", 0}},
|
json{{"name", "offset"}, {"type", "number"}, {"unit", "mm"}, {"default", 0}},
|
||||||
})}},
|
})}},
|
||||||
|
json{{"name", "import_step"}, {"summary", "Import a STEP file as native B-rep bodies (the reference part to measure)."},
|
||||||
|
{"params", json::array({
|
||||||
|
json{{"name", "path"}, {"type", "string"}},
|
||||||
|
})}},
|
||||||
|
json{{"name", "validate_against"}, {"summary", "Volume + bbox/centroid deviation of a body vs a reference {step:path|body:id} (the RE acceptance metric)."},
|
||||||
|
{"params", json::array({
|
||||||
|
json{{"name", "body"}, {"type", "integer"}, {"default", 0}},
|
||||||
|
json{{"name", "reference"}, {"type", "object"}},
|
||||||
|
})}},
|
||||||
})},
|
})},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -271,6 +286,83 @@ json slice_body(DesignPanel* panel, const json& params)
|
|||||||
{"segment_count", int(polylines.size())}, {"polylines", std::move(polylines)}};
|
{"segment_count", int(polylines.size())}, {"polylines", std::move(polylines)}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Build: bring a reference part in (Import STEP as native B-rep bodies) ------
|
||||||
|
json import_step(DesignPanel* panel, const json& params)
|
||||||
|
{
|
||||||
|
if (!params.contains("path")) throw std::runtime_error("import_step needs 'path'");
|
||||||
|
const std::string path = params["path"].get<std::string>();
|
||||||
|
std::string err;
|
||||||
|
std::vector<TopoDS_Shape> solids = GeometryEngine::read_step_solids(path, err);
|
||||||
|
if (solids.empty()) throw std::runtime_error(err.empty() ? "no solids in STEP" : err);
|
||||||
|
|
||||||
|
CadDocument& doc = panel->mcp_doc();
|
||||||
|
doc.checkpoint();
|
||||||
|
int first = int(doc.features.size());
|
||||||
|
for (const TopoDS_Shape& s : solids) {
|
||||||
|
CadFeature f;
|
||||||
|
f.type = CadFeatureType::Import;
|
||||||
|
f.name = "STEP" + std::to_string(int(doc.features.size()) + 1);
|
||||||
|
f.imported_solid = s;
|
||||||
|
f.mode = BooleanMode::New; // each solid = its own coexisting body
|
||||||
|
doc.features.push_back(f);
|
||||||
|
}
|
||||||
|
bool ok = doc.recompute();
|
||||||
|
if (!ok) doc.undo();
|
||||||
|
panel->mcp_after_change();
|
||||||
|
return json{{"ok", ok}, {"imported", int(solids.size())}, {"first_feature", first},
|
||||||
|
{"bodies", int(doc.bodies.size())}, {"error", doc.error}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Validate: volume + bbox deviation of a body vs a reference (the "scarto %") --
|
||||||
|
// ponytail: volume delta + bbox/centroid offset (the RE skill's actual acceptance metric).
|
||||||
|
// Surface-deviation heat-map is the upgrade path (per-vertex BRepExtrema), add when needed.
|
||||||
|
struct ShapeMetrics { double volume; Vec3d centroid, bmin, bmax; };
|
||||||
|
ShapeMetrics shape_metrics(const TopoDS_Shape& s)
|
||||||
|
{
|
||||||
|
GProp_GProps vp; BRepGProp::VolumeProperties(s, vp);
|
||||||
|
gp_Pnt c = vp.CentreOfMass();
|
||||||
|
Bnd_Box bb; BRepBndLib::Add(s, bb);
|
||||||
|
Standard_Real x0, y0, z0, x1, y1, z1; bb.Get(x0, y0, z0, x1, y1, z1);
|
||||||
|
return {vp.Mass(), Vec3d(c.X(), c.Y(), c.Z()), Vec3d(x0, y0, z0), Vec3d(x1, y1, z1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
json validate_against(DesignPanel* panel, const json& params)
|
||||||
|
{
|
||||||
|
const TopoDS_Shape& cand = body_shape(panel, params); // candidate = the reconstruction
|
||||||
|
if (!params.contains("reference")) throw std::runtime_error("validate_against needs 'reference' {step|body}");
|
||||||
|
const json& r = params["reference"];
|
||||||
|
|
||||||
|
TopoDS_Shape ref;
|
||||||
|
if (r.contains("step")) {
|
||||||
|
std::string err;
|
||||||
|
std::vector<TopoDS_Shape> solids = GeometryEngine::read_step_solids(r["step"].get<std::string>(), err);
|
||||||
|
if (solids.empty()) throw std::runtime_error(err.empty() ? "reference STEP has no solids" : err);
|
||||||
|
BRep_Builder b; TopoDS_Compound comp; b.MakeCompound(comp);
|
||||||
|
for (const TopoDS_Shape& s : solids) if (!s.IsNull()) b.Add(comp, s);
|
||||||
|
ref = comp;
|
||||||
|
} else if (r.contains("body")) {
|
||||||
|
CadDocument& doc = panel->mcp_doc();
|
||||||
|
int idx = r["body"].get<int>();
|
||||||
|
if (idx < 0 || idx >= int(doc.bodies.size()) || doc.bodies[idx].shape.IsNull())
|
||||||
|
throw std::runtime_error("reference body index out of range / null");
|
||||||
|
ref = doc.bodies[idx].shape;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("reference must be {\"step\": path} or {\"body\": id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapeMetrics a = shape_metrics(cand), b = shape_metrics(ref);
|
||||||
|
double dv = b.volume > 0 ? (a.volume - b.volume) / b.volume * 100.0 : 0.0;
|
||||||
|
Vec3d coff = a.centroid - b.centroid;
|
||||||
|
Vec3d dmin = a.bmin - b.bmin, dmax = a.bmax - b.bmax;
|
||||||
|
return json{
|
||||||
|
{"volume", a.volume}, {"volume_reference", b.volume}, {"volume_delta_pct", dv},
|
||||||
|
{"centroid_offset", vec3(coff)}, {"centroid_offset_mm", coff.norm()},
|
||||||
|
{"bbox", json{{"min", vec3(a.bmin)}, {"max", vec3(a.bmax)}}},
|
||||||
|
{"bbox_reference", json{{"min", vec3(b.bmin)}, {"max", vec3(b.bmax)}}},
|
||||||
|
{"bbox_delta", json{{"min", vec3(dmin)}, {"max", vec3(dmax)}}},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
json action_extrude(DesignPanel* panel, const json& params)
|
json action_extrude(DesignPanel* panel, const json& params)
|
||||||
{
|
{
|
||||||
const double w = params.value("width", 20.0);
|
const double w = params.value("width", 20.0);
|
||||||
@@ -310,6 +402,8 @@ std::string handle_on_main(const std::string& method, const json& params, const
|
|||||||
if (method == "query_topology") return rpc_result(id, query_topology(panel, params));
|
if (method == "query_topology") return rpc_result(id, query_topology(panel, params));
|
||||||
if (method == "measure") return rpc_result(id, measure(panel, params));
|
if (method == "measure") return rpc_result(id, measure(panel, params));
|
||||||
if (method == "slice_body") return rpc_result(id, slice_body(panel, params));
|
if (method == "slice_body") return rpc_result(id, slice_body(panel, params));
|
||||||
|
if (method == "import_step") return rpc_result(id, import_step(panel, params));
|
||||||
|
if (method == "validate_against") return rpc_result(id, validate_against(panel, params));
|
||||||
if (method == "extrude") return rpc_result(id, action_extrude(panel, params));
|
if (method == "extrude") return rpc_result(id, action_extrude(panel, params));
|
||||||
return rpc_error(id, -32601, "Unknown method: " + method);
|
return rpc_error(id, -32601, "Unknown method: " + method);
|
||||||
} catch (const Standard_Failure& ex) { // OCCT errors are NOT std::exception
|
} catch (const Standard_Failure& ex) { // OCCT errors are NOT std::exception
|
||||||
|
|||||||
Reference in New Issue
Block a user