mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-16 12:21:11 +00:00
Design tab: revolve about a line of the sketch
Reported: the Revolve axis could only be the sketch plane's X or Y axis through its origin, so a half-profile drawn beside a centerline, the usual way, could not be revolved about that centerline. - CadFeature::revolve_axis_entity names a Line of the profile sketch to revolve about (a construction centerline, or an edge of the profile itself); -1 keeps revolve_axis. Appended at the end of the framed recipe, so existing projects load and rebuild unchanged. Revolve and Surface Revolve resolve their axis in one place (revolve_axis_of); an index that no longer names a line fails with a reason. - SketchEngine::make_revolve takes the world axis. A profile with points on both sides of it is refused with "the profile crosses the revolve axis"; MakeRevol failed there with no reason. - The Axis list of both cards reads Plane X, Plane Y, then every line of the sketch, named as the constraint list names them (Centerline E4, Line E3). A fresh revolve preselects the sketch's centerline when it has exactly one. The gizmo turns about the chosen axis and draws it dashed; construction lines no longer pull its centre. Tests: a rectangle beside a construction centerline revolves into the tube of the expected volume along that line; about its own edge, into a cylinder; an axis through it is refused with the reason; a stale axis index fails with a reason; the axis survives save and load. The truncated-recipe test accounts for the new tail field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
This commit is contained in:
@@ -2351,11 +2351,12 @@ TEST_CASE("a truncated feature keeps what it could read", "[CadDocument][recipe]
|
||||
// Shorten feature 1 so it ends right after coordsys_face_kind: drop coordsys_face_edges
|
||||
// (4 bytes), the two flags appended after it (thread_major_nominal, pattern_inclusive:
|
||||
// 1 byte each), the empty dressup_edges list and the two empty text strings (an 8-byte size
|
||||
// tag each) and text_height (a double). Rewrite its length prefix and erase the tail bytes.
|
||||
// tag each), text_height (a double) and revolve_axis_entity (an int). Rewrite its length prefix and erase the tail bytes.
|
||||
// The reader then runs out inside fa(f), throws, and keeps everything it had already
|
||||
// assigned — that is the whole point of the try/catch. (Cut on a field boundary: a field
|
||||
// cut in half is read as whatever half arrived.)
|
||||
const size_t drop = sizeof(uint32_t) + 2 * sizeof(bool) + 3 * sizeof(cereal::size_type) + sizeof(double);
|
||||
const size_t drop = sizeof(uint32_t) + 2 * sizeof(bool) + 3 * sizeof(cereal::size_type) + sizeof(double)
|
||||
+ sizeof(int);
|
||||
REQUIRE(f_len[1] > drop);
|
||||
std::string shortened = blob;
|
||||
shortened.erase(f_off[1] + 4 + f_len[1] - drop, drop);
|
||||
@@ -8948,3 +8949,93 @@ TEST_CASE("display edges: every real edge once, no seams, no degenerate apex", "
|
||||
const auto cone = GeometryEngine::display_edges(BRepPrimAPI_MakeCone(5., 0., 8.).Shape(), 0.01);
|
||||
CHECK(cone.size() == 1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// A 10 x 20 rectangle standing on the plane X axis between u = 5 and u = 15, and a construction
|
||||
// centerline x = 0 from (0,0) to (0,20): the half-profile of a tube, drawn the usual way.
|
||||
Slic3r::CadFeature tube_half_profile()
|
||||
{
|
||||
using namespace Slic3r;
|
||||
CadFeature sk;
|
||||
sk.type = CadFeatureType::Sketch;
|
||||
sk.plane = SketchPlane::XY();
|
||||
auto line = [](Vec2d a, Vec2d b, bool c) {
|
||||
SketchEntity e; e.type = SketchEntity::Type::Line; e.p0 = a; e.p1 = b; e.construction = c; return e; };
|
||||
sk.entities = { line({5, 0}, {15, 0}, false), line({15, 0}, {15, 20}, false),
|
||||
line({15, 20}, {5, 20}, false), line({5, 20}, {5, 0}, false),
|
||||
line({0, 0}, {0, 20}, true) };
|
||||
return sk;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("revolve about a line of the sketch", "[CadDocument][revolve]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
SECTION("a construction centerline: the profile sweeps into a tube around it") {
|
||||
CadDocument doc;
|
||||
doc.features.push_back(tube_half_profile());
|
||||
const int r = doc.add_revolve(0, 360.0, 0, false, BooleanMode::New, "Rev");
|
||||
doc.features[r].revolve_axis_entity = 4;
|
||||
REQUIRE(doc.recompute());
|
||||
REQUIRE(doc.error.empty());
|
||||
REQUIRE(doc.bodies.size() == 1);
|
||||
GProp_GProps props;
|
||||
BRepGProp::VolumeProperties(doc.bodies[0].shape, props);
|
||||
CHECK(props.Mass() == Approx(M_PI * (15. * 15. - 5. * 5.) * 20.).epsilon(1e-6)); // 4000 pi
|
||||
Bnd_Box box;
|
||||
BRepBndLib::Add(doc.bodies[0].shape, box);
|
||||
double x0, y0, z0, x1, y1, z1;
|
||||
box.Get(x0, y0, z0, x1, y1, z1);
|
||||
CHECK(y0 == Approx(0.).margin(0.01)); // the axis runs along Y, as drawn
|
||||
CHECK(y1 == Approx(20.).margin(0.01));
|
||||
CHECK(x1 == Approx(15.).margin(0.01));
|
||||
}
|
||||
|
||||
SECTION("an edge of the profile itself: a solid cylinder") {
|
||||
CadDocument doc;
|
||||
doc.features.push_back(tube_half_profile());
|
||||
const int r = doc.add_revolve(0, 360.0, 0, false, BooleanMode::New, "Rev");
|
||||
doc.features[r].revolve_axis_entity = 3; // the rectangle's left side, u = 5
|
||||
REQUIRE(doc.recompute());
|
||||
REQUIRE(doc.error.empty());
|
||||
GProp_GProps props;
|
||||
BRepGProp::VolumeProperties(doc.bodies[0].shape, props);
|
||||
CHECK(props.Mass() == Approx(M_PI * 10. * 10. * 20.).epsilon(1e-6));
|
||||
}
|
||||
|
||||
SECTION("an axis through the profile is refused with the reason") {
|
||||
CadDocument doc;
|
||||
CadFeature sk = tube_half_profile();
|
||||
sk.entities[4].p0 = Vec2d(10, 0);
|
||||
sk.entities[4].p1 = Vec2d(10, 20); // straight through the middle of the rectangle
|
||||
doc.features.push_back(sk);
|
||||
const int r = doc.add_revolve(0, 360.0, 0, false, BooleanMode::New, "Rev");
|
||||
doc.features[r].revolve_axis_entity = 4;
|
||||
CHECK_FALSE(doc.recompute());
|
||||
INFO(doc.error);
|
||||
CHECK(doc.error.find("crosses the revolve axis") != std::string::npos);
|
||||
}
|
||||
|
||||
SECTION("an axis index that no longer names a line fails with a reason") {
|
||||
CadDocument doc;
|
||||
doc.features.push_back(tube_half_profile());
|
||||
const int r = doc.add_revolve(0, 360.0, 0, false, BooleanMode::New, "Rev");
|
||||
doc.features[r].revolve_axis_entity = 9;
|
||||
CHECK_FALSE(doc.recompute());
|
||||
CHECK(doc.error.find("axis line") != std::string::npos);
|
||||
}
|
||||
|
||||
SECTION("the axis line survives save and load") {
|
||||
CadDocument doc;
|
||||
doc.features.push_back(tube_half_profile());
|
||||
const int r = doc.add_revolve(0, 270.0, 0, false, BooleanMode::New, "Rev");
|
||||
doc.features[r].revolve_axis_entity = 4;
|
||||
REQUIRE(doc.recompute());
|
||||
CadDocument loaded;
|
||||
REQUIRE(loaded.deserialize_recipe(doc.serialize_recipe()));
|
||||
REQUIRE(loaded.features.size() == 2);
|
||||
CHECK(loaded.features[1].revolve_axis_entity == 4);
|
||||
CHECK(loaded.features[1].revolve_angle == Approx(270.));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user