A body carries its own name, because a body is not its first feature

User report 2026-08-23, and it is right: "you have renamed the feature extrusion,
not the body. i clicked rename on the body feature tree and the feature extrude
changed name. this means that you consider the extrusion = the body. this is very
far from truth as a body can contain several extrusions."

That is exactly what the previous commit did. It resolved a selected body to
CadBody::source_feature and renamed THAT feature, on the reasoning that a body has
no name of its own. The reasoning described an implementation detail — CadBody::
name is derived and restamped on every recompute — and mistook it for the user's
model. An Extrude, a Cut and a Fillet all land on the same body: the maker is one
operation in its history, and renaming it renames the wrong object.

A body now has a name of its own. CadBody::user_name, set only by a rename, is:
  - carried across recompute() by body index, next to the per-body colour override
    and under the same index contract the GUI already relies on for visibility and
    Move — without which a name would survive exactly until the next feature;
  - written into the recipe, because bodies are recomputed and never serialised, so
    a name has nowhere else to live and would otherwise vanish on reopen;
  - shown on the Bodies row ahead of the derived maker name, as "Body N — name",
    with the number still leading because every status line, the interference
    report and the mate errors identify a body that way.

The recipe block is APPENDED after the variables block rather than given a version
bump. A build that predates it reads features and variables, returns, and never
looks at the trailing bytes — so yesterday's projects open here and today's
projects still open there. A bump would have cost every project written today its
readability by the previous build, for one optional field.

Renaming a FEATURE is unchanged. The feature tree renames features; the Bodies
list renames bodies; neither reaches into the other.

WHAT THIS COST, and why it is written down. Getting here took two wrong turns
inside one fix, both mine:

  1. UnselectAll() -> Unselect(). Both trees are wxTR_SINGLE, where UnselectAll()
     — the MULTI-selection call — does nothing. That was the one-word reason the
     rename had been vetoed everywhere (BEGIN_LABEL_EDIT refuses while
     tree_body_selection() >= 0). Fixing it turned a pair of harmless no-ops into
     a real loop: the two lists clear each other so "the target" is unambiguous,
     so clicking a body row ran apply_body_row -> m_tree->Unselect() -> the feature
     tree's SEL_CHANGED -> m_parts->Unselect(), which cleared the row just clicked.
     The handler now clears the other list only when it actually holds a selection.
  2. Trusting a screenshot taken after a polluted run. A leftover Rib card had
     shifted the whole panel, so a click measured against it landed nowhere near
     the row. Relaunch, then measure.

VERIFIED, on the rig and in the kernel:
  body renamed          ('Extrude', user_name=False) -> ('Bracket', user_name=True)
  features untouched    ['Sketch', 'Extrude'] before and after
  survives a recompute  add a Hole to the same body: features become
                        ['Sketch', 'Extrude', 'Hole'], body stays 'Bracket'
  survives the recipe   serialize -> deserialize -> 'Bracket'

New kernel test "a body carries its own name, through recompute and the recipe"
pins all three properties; the suite is 189 cases / 2547 assertions.

Gate green: ALL LADDERS HELD — offer table matches the atlas, kernel suite,
engine rungs 1-8, 977-sheet corpus + the heaviest sheets, gesture ladder 98/98,
offer ladder 108/108.
This commit is contained in:
Tommaso Bianchi
2026-08-23 19:09:17 +02:00
parent 0ac9ac91f7
commit 6e7f6429fa
5 changed files with 168 additions and 40 deletions
+48
View File
@@ -8097,3 +8097,51 @@ TEST_CASE("A sketch-only document recomputes and round-trips", "[CadDocument]")
REQUIRE_FALSE(bad.recompute());
REQUIRE_FALSE(bad.error.empty());
}
// A body is NOT the feature that created it. Reported 2026-08-23, in these words: "you have
// renamed the feature extrusion, not the body ... this means that you consider the extrusion =
// the body, which is very far from truth as a body can contain several extrusions." The rename
// used to resolve CadBody::source_feature and rename THAT, so naming a body edited one operation
// in its history. A body now carries its own name, and this pins the three properties that make
// it a name rather than a label: it does not touch the features, it survives a recompute that
// adds more features to the same body, and it survives the recipe round trip.
TEST_CASE("a body carries its own name, through recompute and the recipe", "[CadDocument]")
{
CadDocument doc;
std::vector<SketchEntity> ents;
auto line = [&](double x0, double y0, double x1, double y1) {
SketchEntity e; e.type = SketchEntity::Type::Line;
e.p0 = Vec2d(x0, y0); e.p1 = Vec2d(x1, y1); ents.push_back(e); };
line(0, 0, 40, 0); line(40, 0, 40, 30); line(40, 30, 0, 30); line(0, 30, 0, 0);
const int sk = doc.add_sketch_entities(ents, SketchPlane::XY(), "Profile");
doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Extrude");
REQUIRE(doc.recompute());
REQUIRE(doc.bodies.size() == 1);
doc.bodies[0].has_user_name = true;
doc.bodies[0].user_name = "Bracket";
// A SECOND feature lands on the same body — the case the report is about.
doc.add_hole(6.0, 5.0, true, 20.0, 15.0, SketchPlane::XY(), "Hole");
REQUIRE(doc.recompute());
REQUIRE(doc.bodies.size() == 1);
CHECK(doc.bodies[0].has_user_name);
CHECK(doc.bodies[0].user_name == "Bracket");
// The features keep their own names: renaming the body renamed nothing else.
REQUIRE(doc.features.size() == 3);
CHECK(doc.features[0].name == "Profile");
CHECK(doc.features[1].name == "Extrude");
CHECK(doc.features[2].name == "Hole");
// ...and the name is part of what gets saved. Bodies are recomputed, never serialised, so
// without the recipe block a body name would live exactly until the project was reopened.
const std::string blob = doc.serialize_recipe();
REQUIRE_FALSE(blob.empty());
CadDocument fresh;
REQUIRE(fresh.deserialize_recipe(blob));
REQUIRE(fresh.bodies.size() == 1);
CHECK(fresh.bodies[0].has_user_name);
CHECK(fresh.bodies[0].user_name == "Bracket");
}