mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 11:51:24 +00:00
A reference is a reference whatever feature holds it
Port of snaporca 1bb9825db0. Four defects from an independent 20-agent audit, each verified in the code first; two further findings from the same report were verified OUT and are not in this commit. remove_feature()/move_feature() remapped Extrude::sketch_ref and a Mate's two connectors and nothing else, leaving seven of the nine index-bearing fields — sweep_path_ref, loft_profile_refs[], pattern_curve_sketch, rib_sketch_ref, and sketch_ref on Revolve, Sweep, Rib and the Surface* family — pointing at whatever slid into the slot. Quiet by construction: the shifted index still names a real feature, recompute() succeeds, the solid is built from the wrong profile. The comment above the loop already required "EVERY field holding a feature index"; the code under it handled two, because a type switch is only correct on the day it is written. for_each_feature_ref() visits the FIELDS instead, so a feature type added later is covered the moment it reuses one. plane_base and axis_plane_a/b are excluded on purpose and documented at the helper — they encode an ordinal into the datum-plane list, not an index into features[], and are filed separately. The delete cascade got the same field-based treatment. The regression test was run against the pre-fix code to prove it bites: all three sections fail there, and move_feature returns TRUE while leaving sketch_ref == 1 where it must be 0 — success with the wrong answer, which is what makes this class expensive. apply_constraint, commit_entity_constraints and delete_constraint mutated the recipe with no checkpoint() and no sync_recipe_to_model(), alone among seventeen mutation sites in that file: Ctrl+Z reached past the constraint edit and discarded unrelated work, and saving persisted the pre-constraint blob. A rejected constraint now calls abandon_checkpoint() rather than leaving an undo step that does nothing. MCP: params["generation"].get<uint64_t>() sat outside the try inside a bare CallAfter lambda, so one malformed string terminated the process through the wx event loop; it is type-checked now and the lambda lets nothing escape. The socket bound with no mode of its own in a world-writable directory — umask around bind() plus chmod, and it refuses to listen rather than listen wide. The reply write is no longer a bare write(), which could SIGPIPE the app when a client hung up. Kernel suite on this fork: 190 cases / 2562 assertions, green. GUI target compiles. The full ladder gate ran on snaporca (ALL LADDERS HELD — gestures 98/98, offer 108/108, corpus and corpus-scale green) and fork-check parity holds at 17 identical / 8 diverging as expected, which is what makes that gate transferable here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MrMzTpAf78U4NG2M8jfvHY
This commit is contained in:
co-authored by
Claude Opus 5
parent
6e7f6429fa
commit
3fd5c3353a
@@ -1859,6 +1859,12 @@ void CadDocument::checkpoint()
|
||||
m_undo.erase(m_undo.begin());
|
||||
}
|
||||
|
||||
void CadDocument::abandon_checkpoint()
|
||||
{
|
||||
if (!m_undo.empty())
|
||||
m_undo.pop_back();
|
||||
}
|
||||
|
||||
bool CadDocument::undo()
|
||||
{
|
||||
if (m_undo.empty())
|
||||
@@ -1915,6 +1921,33 @@ static bool commit_or_rollback(CadDocument& doc, std::vector<CadFeature>& snapsh
|
||||
return false;
|
||||
}
|
||||
|
||||
// Visit every field of `f` that holds an index into features[].
|
||||
//
|
||||
// Deliberately NOT dispatched on f.type. A type switch is what this replaced, and it was
|
||||
// wrong in the way type switches go wrong: it listed Extrude and Mate, and every feature
|
||||
// type added afterwards — Revolve, Sweep, Loft, Rib, Pattern-on-curve, the Surface* family —
|
||||
// silently inherited a remap that skipped its references. Visiting the FIELDS instead means
|
||||
// a new type is covered the moment it reuses one of them, and reaching a field its type
|
||||
// never reads costs nothing: unset refs are -1 and every visitor here ignores those.
|
||||
//
|
||||
// The list is exactly the fields documented as "index into features[]" / "feature index" in
|
||||
// CadDocument.hpp. Not included, because they are a different basis and need their own pass:
|
||||
// plane_base / axis_plane_a / axis_plane_b encode `3 + N` = the Nth DATUM PLANE, an ordinal
|
||||
// into resolve_datum_planes(), not into features[]. Everything named *_body / *_face / *_edge
|
||||
// is a body index or a global topology id and must never be remapped here.
|
||||
template<class Visit>
|
||||
static void for_each_feature_ref(CadFeature& f, Visit&& visit)
|
||||
{
|
||||
visit(f.sketch_ref);
|
||||
visit(f.sweep_path_ref);
|
||||
visit(f.pattern_curve_sketch);
|
||||
visit(f.rib_sketch_ref);
|
||||
visit(f.mate_cs_a);
|
||||
visit(f.mate_cs_b);
|
||||
for (int& r : f.loft_profile_refs)
|
||||
visit(r);
|
||||
}
|
||||
|
||||
bool CadDocument::remove_feature(int index)
|
||||
{
|
||||
if (index < 0 || index >= int(features.size()))
|
||||
@@ -1922,13 +1955,22 @@ bool CadDocument::remove_feature(int index)
|
||||
|
||||
std::vector<CadFeature> snapshot = features;
|
||||
|
||||
// Deleting a Sketch cascades to every Extrude that consumes it (a dangling
|
||||
// Extrude would have no wire). A lone Sketch, by contrast, is harmless.
|
||||
// Deleting a Sketch cascades to every feature that consumes it AS ITS PROFILE — the
|
||||
// reason is the original one ("a dangling Extrude would have no wire"), and it applies
|
||||
// unchanged to Revolve, Sweep, Rib and the Surface* family, which all read the profile
|
||||
// through sketch_ref, plus the sweep spine and the rib line. Testing the FIELD rather
|
||||
// than the type is what makes that true without a list to keep up to date.
|
||||
//
|
||||
// pattern_curve_sketch and loft_profile_refs are deliberately NOT cascaded: a Pattern or
|
||||
// a Loft that loses one of several inputs is degraded, not meaningless, so those refs go
|
||||
// to -1 in the remap below and the feature survives. A lone Sketch is harmless either way.
|
||||
std::vector<int> remove{index};
|
||||
if (features[index].type == CadFeatureType::Sketch) {
|
||||
for (int j = 0; j < int(features.size()); ++j)
|
||||
if (features[j].type == CadFeatureType::Extrude && features[j].sketch_ref == index)
|
||||
for (int j = 0; j < int(features.size()); ++j) {
|
||||
const CadFeature& c = features[j];
|
||||
if (c.sketch_ref == index || c.sweep_path_ref == index || c.rib_sketch_ref == index)
|
||||
remove.push_back(j);
|
||||
}
|
||||
}
|
||||
std::sort(remove.begin(), remove.end());
|
||||
remove.erase(std::unique(remove.begin(), remove.end()), remove.end());
|
||||
@@ -1956,14 +1998,8 @@ bool CadDocument::remove_feature(int index)
|
||||
ref -= shift;
|
||||
}
|
||||
};
|
||||
for (auto& f : features) {
|
||||
if (f.type == CadFeatureType::Extrude) {
|
||||
remap(f.sketch_ref);
|
||||
} else if (f.type == CadFeatureType::Mate) {
|
||||
remap(f.mate_cs_a);
|
||||
remap(f.mate_cs_b);
|
||||
}
|
||||
}
|
||||
for (auto& f : features)
|
||||
for_each_feature_ref(f, remap);
|
||||
|
||||
return commit_or_rollback(*this, snapshot);
|
||||
}
|
||||
@@ -1985,14 +2021,8 @@ bool CadDocument::move_feature(int index, int delta)
|
||||
if (ref == index) ref = target;
|
||||
else if (ref == target) ref = index;
|
||||
};
|
||||
for (auto& f : features) {
|
||||
if (f.type == CadFeatureType::Extrude) {
|
||||
swap_ref(f.sketch_ref);
|
||||
} else if (f.type == CadFeatureType::Mate) {
|
||||
swap_ref(f.mate_cs_a);
|
||||
swap_ref(f.mate_cs_b);
|
||||
}
|
||||
}
|
||||
for (auto& f : features)
|
||||
for_each_feature_ref(f, swap_ref);
|
||||
|
||||
return commit_or_rollback(*this, snapshot);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user