mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-06 23:31:19 +00:00
CAD kernel: fix the sketch solver, trim/offset/mirror, and history references
Sketch layer - Trim/extend of an arc (and a circle opened into an arc) rewrites p0/p1 from the new angles; the wire builder, the solver and snapping read them. - Tangency binds the arc end that touches the line (or the other arc) instead of always the start, so a fillet tangent to both legs solves; circle-circle / circle-arc tangency uses the centre distance instead of CURVE_CURVE_TANGENT, which aborts on a circle. - Point-on-line distances and circle-line tangency keep the side the geometry is on; a point on an arc's rim uses PT_ON_CIRCLE. - The partitioned solve keeps constraints onto the origin/axes and counts free entities' DOF. - Zero-radius circles get no solver primitive and build no wire; constraints the solver cannot apply are reported in SketchSolveResult::skipped. - EllipseArc: mirror no longer yields the complement; after a solve its angles and ends are re-derived; on the XZ plane it is no longer built mirrored. - Offset: a circle follows the "+d = left of travel" rule (it shrinks, like a CCW arc chain); chains are joined at the weld tolerance. Bridge end pole fixed (G1, no cusp). Negative-scale transforms keep arcs and ellipses on their ends. Inference tolerances aligned with the weld. Model layer - Body references follow the body across delete / reorder / hide (resolved by the feature that made it); datum-plane ordinals are re-pointed; an index past the end is an error, not "the last body". New set_feature_enabled(). A move that puts a consumer above its input is refused. - Threads made from now on read thread_radius as the nominal major radius (internal: bore to minor, groove to major; external: groove cut into the rod); older recipes build as before. Bad thread parameters say why. Circular patterns span their angle end to end (new ones); add_pattern pivots on the modeling origin. - clear() drops variables; expression fields the GUI offers are bindable (thread_diameter, helix_*, thicken_thickness, ...); deg()/rad() in expressions; the recipe saves the modeling origin and body colours; names/colours follow bodies by identity. - Boolean and dress-up failures throw instead of returning the input; one produces_body(); hole standards corrected (82° inch countersinks, UNC names, #10-24); legacy profile solve validates indices and writes back only on success; v4 recipes read with a frozen field list. Tests cover each of the above. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
This commit is contained in:
@@ -275,21 +275,21 @@ FaceGroup GeometryEngine::classify_face(const TopoDS_Face& face, const TopoDS_Sh
|
||||
|
||||
std::vector<TopoDS_Edge> GeometryEngine::collect_edges(const TopoDS_Shape& solid, FaceGroup target)
|
||||
{
|
||||
// Each edge ONCE. An explorer visits a shared edge from both of its faces, so walking one
|
||||
// handed every edge to the fillet twice; the de-duplicated map is also what edge_by_index
|
||||
// numbers edges by.
|
||||
std::vector<TopoDS_Edge> result;
|
||||
TopTools_IndexedDataMapOfShapeListOfShape edgeFaceMap;
|
||||
TopExp::MapShapesAndAncestors(solid, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
|
||||
if (target == FaceGroup::All) {
|
||||
for (TopExp_Explorer exp(solid, TopAbs_EDGE); exp.More(); exp.Next())
|
||||
result.push_back(TopoDS::Edge(exp.Current()));
|
||||
for (int i = 1; i <= edgeFaceMap.Extent(); ++i)
|
||||
result.push_back(TopoDS::Edge(edgeFaceMap.FindKey(i)));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Build edge-to-face map once
|
||||
TopTools_IndexedDataMapOfShapeListOfShape edgeFaceMap;
|
||||
TopExp::MapShapesAndAncestors(solid, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
|
||||
|
||||
for (TopExp_Explorer edgeExp(solid, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) {
|
||||
const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current());
|
||||
if (!edgeFaceMap.Contains(edge)) continue;
|
||||
const TopTools_ListOfShape& faces = edgeFaceMap.FindFromKey(edge);
|
||||
for (int ei = 1; ei <= edgeFaceMap.Extent(); ++ei) {
|
||||
const TopoDS_Edge& edge = TopoDS::Edge(edgeFaceMap.FindKey(ei));
|
||||
const TopTools_ListOfShape& faces = edgeFaceMap.FindFromIndex(ei);
|
||||
|
||||
bool include = false;
|
||||
for (auto it = faces.begin(); it != faces.end(); ++it) {
|
||||
@@ -325,12 +325,14 @@ std::vector<TopoDS_Edge> GeometryEngine::collect_edges(const TopoDS_Shape& solid
|
||||
|
||||
// ---- Fillet/Chamfer ----
|
||||
|
||||
// All four dress-up entry points fail the same way — with a reason — instead of two of them
|
||||
// handing the solid back unchanged, which recompute then reported as a success.
|
||||
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, FaceGroup faces)
|
||||
{
|
||||
if (radius <= 0.001) return solid;
|
||||
if (radius <= 0.001) throw std::runtime_error("the fillet radius must be greater than 0");
|
||||
|
||||
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
|
||||
if (edges.empty()) return solid;
|
||||
if (edges.empty()) throw std::runtime_error("no edges to fillet in that group");
|
||||
|
||||
BRepFilletAPI_MakeFillet fillet(solid);
|
||||
for (const auto& edge : edges)
|
||||
@@ -346,10 +348,10 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, FaceGroup faces)
|
||||
{
|
||||
if (distance <= 0.001) return solid;
|
||||
if (distance <= 0.001) throw std::runtime_error("the chamfer distance must be greater than 0");
|
||||
|
||||
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
|
||||
if (edges.empty()) return solid;
|
||||
if (edges.empty()) throw std::runtime_error("no edges to chamfer in that group");
|
||||
|
||||
BRepFilletAPI_MakeChamfer chamfer(solid);
|
||||
for (const auto& edge : edges)
|
||||
@@ -362,7 +364,7 @@ TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double dis
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, int edge_id)
|
||||
{
|
||||
if (radius <= 0.001) return solid;
|
||||
if (radius <= 0.001) throw std::runtime_error("the fillet radius must be greater than 0");
|
||||
|
||||
TopoDS_Edge edge = edge_by_index(solid, edge_id);
|
||||
if (edge.IsNull()) throw std::runtime_error("apply_fillet: invalid edge id");
|
||||
@@ -377,7 +379,7 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, int edge_id)
|
||||
{
|
||||
if (distance <= 0.001) return solid;
|
||||
if (distance <= 0.001) throw std::runtime_error("the chamfer distance must be greater than 0");
|
||||
|
||||
TopoDS_Edge edge = edge_by_index(solid, edge_id);
|
||||
if (edge.IsNull()) throw std::runtime_error("apply_chamfer: invalid edge id");
|
||||
|
||||
Reference in New Issue
Block a user