From b355d04d3a20f59f47efe4bc23f97c33526a00d3 Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Mon, 29 Jun 2026 06:30:01 +0200 Subject: [PATCH] CAD re-edit: mid-timeline rebuild tests + non-silent Edit fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror of snaporca-cad a82cb0d. snaporca-88g — three [CadDocument] tests proving recompute() replays the whole timeline, so editing any feature (not just the last) rebuilds downstream: mid-timeline extrude edit -> fillet rebuilds; first-feature sketch edit propagates the chain; the same survives serialize_recipe()/deserialize_recipe(); a sketch->extrude->hole->chamfer chain rebuilds hole+chamfer on a mid-edited extrude. All 3 pass (34 assertions, Catch2 v3). on_edit_feature edits the tree-selected feature at any position (13/16 types); Import/Boolean/Cut get a status message instead of a silent no-op (full dialogs = follow-up snaporca-nu9). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q --- src/slic3r/GUI/DesignPanel.cpp | 8 ++- tests/libslic3r/test_caddocument.cpp | 95 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index b68f6026f3..21a81b6d8f 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -4498,7 +4498,13 @@ void DesignPanel::on_edit_feature() load_feature_into_dialog(f); open_tool(Tool::Draft); break; - default: break; + default: + // Import / Boolean / Cut have no parametric edit dialog yet (follow-up + // snaporca-nu9). Don't silently swallow the Edit click — tell the user. + m_status->SetForegroundColour(wxNullColour); + m_status->SetLabel(_L("This feature type can't be edited yet")); + m_status->Refresh(); + break; } } diff --git a/tests/libslic3r/test_caddocument.cpp b/tests/libslic3r/test_caddocument.cpp index 7ce2de9a98..d79bcd0e05 100644 --- a/tests/libslic3r/test_caddocument.cpp +++ b/tests/libslic3r/test_caddocument.cpp @@ -1421,3 +1421,98 @@ TEST_CASE("deserialize_recipe rejects future version", "[CadDocument]") } REQUIRE_FALSE(doc.deserialize_recipe(oss.str())); } + +TEST_CASE("re-edit: editing a mid-timeline feature rebuilds downstream", "[CadDocument]") +{ + using Catch::Matchers::WithinRel; + CadDocument doc; + + int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), + 20, 10, 0, "Sketch1"); + REQUIRE(sk >= 0); + int ex = doc.add_extrude(sk, 5.0, false, BooleanMode::New, "Extrude1"); + REQUIRE(ex >= 0); + doc.add_fillet(1.0, FaceGroup::All, "Fillet1"); + + REQUIRE(doc.recompute()); + REQUIRE(doc.error.empty()); + Vec3d sz0 = doc.display_mesh.bounding_box().size(); + REQUIRE(sz0.z() > 0.0); + + // Edit the MID feature (extrude — NOT the last; Fillet is downstream). + doc.features[ex].distance = 12.0; + REQUIRE(doc.recompute()); + REQUIRE(doc.error.empty()); + Vec3d sz1 = doc.display_mesh.bounding_box().size(); + REQUIRE(sz1.z() > sz0.z() + 1.0); + REQUIRE(doc.bodies.size() == 1); + + // Edit the FIRST feature (sketch width) — must propagate the whole chain. + doc.features[sk].width = 30; + REQUIRE(doc.recompute()); + REQUIRE(doc.error.empty()); + Vec3d sz2 = doc.display_mesh.bounding_box().size(); + REQUIRE(sz2.x() > sz1.x() + 1.0); +} + +TEST_CASE("re-edit survives serialize -> deserialize", "[CadDocument]") +{ + using Catch::Matchers::WithinRel; + CadDocument doc; + + int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), + 20, 10, 0, "Sketch1"); + REQUIRE(sk >= 0); + doc.add_extrude(sk, 5.0, false, BooleanMode::New, "Extrude1"); + doc.add_fillet(1.0, FaceGroup::All, "Fillet1"); + + REQUIRE(doc.recompute()); + REQUIRE(doc.error.empty()); + + auto blob = doc.serialize_recipe(); + REQUIRE_FALSE(blob.empty()); + + CadDocument doc2; + REQUIRE(doc2.deserialize_recipe(blob)); + REQUIRE(doc2.error.empty()); + REQUIRE(doc2.recompute()); + REQUIRE(doc2.error.empty()); + Vec3d sz_pre = doc2.display_mesh.bounding_box().size(); + REQUIRE(sz_pre.z() > 0.0); + + // Mid-edit the deserialized document — the persisted recipe stays re-editable. + doc2.features[1].distance = 12.0; + REQUIRE(doc2.recompute()); + REQUIRE(doc2.error.empty()); + Vec3d sz_post = doc2.display_mesh.bounding_box().size(); + REQUIRE(sz_post.z() > sz_pre.z() + 1.0); +} + +TEST_CASE("re-edit: multi-type timeline replays all downstream features", "[CadDocument]") +{ + using Catch::Matchers::WithinRel; + CadDocument doc; + + int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), + 30, 30, 0, "Sketch1"); + REQUIRE(sk >= 0); + int ex = doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Extrude1"); + REQUIRE(ex >= 0); + doc.add_hole(6.0, 4.0, false, 0.0, 0.0, SketchPlane::XY(), "Hole1"); + doc.add_chamfer(1.0, FaceGroup::All, "Chamfer1"); + + REQUIRE(doc.recompute()); + REQUIRE(doc.error.empty()); + size_t n_bodies = doc.bodies.size(); + REQUIRE(n_bodies >= 1); + Vec3d sz0 = doc.display_mesh.bounding_box().size(); + REQUIRE(sz0.z() > 0.0); + + // Edit the MID extrude — downstream Hole and Chamfer must rebuild. + doc.features[ex].distance = 16.0; + REQUIRE(doc.recompute()); + REQUIRE(doc.error.empty()); + REQUIRE(doc.bodies.size() == n_bodies); + Vec3d sz1 = doc.display_mesh.bounding_box().size(); + REQUIRE(sz1.z() > sz0.z() + 1.0); +}