diff --git a/src/libslic3r/CadDocument.cpp b/src/libslic3r/CadDocument.cpp index c9a001fb78..1050242407 100644 --- a/src/libslic3r/CadDocument.cpp +++ b/src/libslic3r/CadDocument.cpp @@ -1335,6 +1335,27 @@ std::vector CadDocument::mate_options(int cs_a, int cs_ return out; } + // B is the connector on the body that MOVES, so B must belong to one; A is the fixed + // reference and needs no body. Without this every kind was offered on a Point(world) + // connector and the mate only failed at RECOMPUTE, with "mate_cs_b has no associated body" — + // one step too late, after the feature already existed in the tree. The same two conditions + // the apply path throws on are checked here, so the offer and the kernel cannot disagree. + const int body_b = features[cs_b].coordsys_body; + if (body_b < 0) { + for (auto& o : out) { + o.viable = false; + o.reason = "connector B is not attached to a body — a mate moves B's body"; + } + return out; + } + if (body_b >= int(bodies.size()) || bodies[body_b].shape.IsNull()) { + for (auto& o : out) { + o.viable = false; + o.reason = "connector B's body no longer exists"; + } + return out; + } + const int ka = features[cs_a].coordsys_face_kind; const int kb = features[cs_b].coordsys_face_kind; auto face_desc = [](int kind) -> std::string { return kind == GeomAbs_Plane ? "a flat face" : "a curved face"; }; diff --git a/tests/libslic3r/test_caddocument.cpp b/tests/libslic3r/test_caddocument.cpp index 2fced4bbf2..4f388acf8a 100644 --- a/tests/libslic3r/test_caddocument.cpp +++ b/tests/libslic3r/test_caddocument.cpp @@ -7722,6 +7722,43 @@ TEST_CASE("mate_options always returns five entries in kind order", "[CadDocumen for (int i = 0; i < 5; ++i) REQUIRE(opts2[i].kind == i); } +TEST_CASE("a connector with no body dims every mate kind, with a reason", "[CadDocument][mate]") +{ + CadDocument doc; + int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 0, "Box"); + doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Extrude"); + REQUIRE(doc.recompute()); + + // Point(world) connectors belong to no body, which is the default the GUI hands you. + int a = doc.add_coordsys(CoordSysType::PointWorld, Vec3d(0, 0, 0), "A"); + int b = doc.add_coordsys(CoordSysType::PointWorld, Vec3d(30, 0, 0), "B"); + REQUIRE(doc.features[b].coordsys_body < 0); + + // B is the connector whose body MOVES, so without one no kind can apply. The offer used to + // call all five viable and the mate only failed at recompute, after the feature existed. + auto opts = doc.mate_options(a, b); + REQUIRE(opts.size() == 5); + for (int i = 0; i < 5; ++i) { + REQUIRE(opts[i].kind == i); + REQUIRE_FALSE(opts[i].viable); + REQUIRE(opts[i].reason.find("not attached to a body") != std::string::npos); + } + + // A needs no body — it is the fixed reference. Giving B one is enough to revive the offer. + doc.features[b].coordsys_body = 0; + auto opts2 = doc.mate_options(a, b); + REQUIRE(opts2[0].viable); // Fastened is frame-only + REQUIRE(opts2[0].reason.empty()); + + // A body index that no longer resolves is refused too, and says so differently. + doc.features[b].coordsys_body = 7; + auto opts3 = doc.mate_options(a, b); + for (const auto& o : opts3) { + REQUIRE_FALSE(o.viable); + REQUIRE(o.reason.find("no longer exists") != std::string::npos); + } +} + TEST_CASE("a flat-face pair offers Fastened, Planar and Slider but not the axial types", "[CadDocument][mate]") { CadDocument doc; @@ -7767,8 +7804,17 @@ TEST_CASE("a flat-face pair offers Fastened, Planar and Slider but not the axial TEST_CASE("an unrecorded fingerprint does not make a type non-viable", "[CadDocument][mate]") { CadDocument doc; + int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 0, "Box"); + doc.add_extrude(sk, 10.0, false, BooleanMode::New, "Extrude"); + REQUIRE(doc.recompute()); + int a = doc.add_coordsys(CoordSysType::PointWorld, Vec3d(0, 0, 0), "A"); int b = doc.add_coordsys(CoordSysType::PointWorld, Vec3d(1, 0, 0), "B"); + // The MISSING FINGERPRINT is the whole subject here, so nothing else may be missing: B is + // given a body, because a connector without one is refused for that reason instead and the + // case would no longer isolate what this test is named for. + doc.features[a].coordsys_body = 0; + doc.features[b].coordsys_body = 0; // PointWorld connectors never record a face fingerprint: face_kind stays -1. REQUIRE(doc.features[a].coordsys_face_kind == -1); REQUIRE(doc.features[b].coordsys_face_kind == -1);