mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
Horizontal and vertical distance dimensions
Port of snaporca 9fa304c77a. Parity OK: 17 files identical, 8 diverging at their expected counts. The everyday dimension in SolidWorks and Onshape, and this kernel had no form of it. Distance constrains the straight-line gap; LockX/LockY pin one point's ABSOLUTE coordinate. Neither relates two points along an axis. DistanceX/DistanceY emit SLVS_C_PROJ_PT_DISTANCE against two unit direction lines built in the solver's G_FIXED group, so they add no degrees of freedom. THE DIRECTION IS SIGNED, and getting it backwards is silent. libslvs defines a LINE_SEGMENT's direction as point[0] - point[1] (entity.cpp) and PROJ_PT_DISTANCE constrains (pB - pA).dot(dir) (constrainteq.cpp:234), so the reference lines are built head-first to mean +X and +Y. The same signedness was a real defect in the GUI: the inline editor was pre-filled with |delta|, so when the closest endpoint pair ran right-to-left, opening the dimension and accepting the number shown would flip the point to the other side of its anchor. Opening a dimension and accepting its own value must be a no-op. The refs are now ordered so the shown value is positive. On the CAD-1000-hours corpus, dimensioning and constraining is 31.9% of all observed CAD time -- the largest single class, 7.6x feature operations. This is the item in the constraint epic that lands most directly on it. VERIFICATION LIMIT, as with the previous commit: this fork's kernel suite still cannot run (find_package(assimp) fails at configure time, snaporca-w80c). The shared sources are byte-identical to snaporca's, where kernel is 2603 assertions / 200 cases and ALL LADDERS HELD across all seven rungs.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6v12M18 6v12M6 12h12M6 12l2.5-2M6 12l2.5 2M18 12l-2.5-2M18 12l-2.5 2"/></svg>
|
||||
|
After Width: | Height: | Size: 269 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#b6b6b6" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M6 6h12M6 18h12M12 6v12M12 6l-2 2.5M12 6l2 2.5M12 18l-2-2.5M12 18l2-2.5"/></svg>
|
||||
|
After Width: | Height: | Size: 269 B |
@@ -88,7 +88,9 @@ enum class SketchConstraintType {
|
||||
// Append-only: cereal serializes this enum positionally as its underlying int, so
|
||||
// inserting anywhere but the end reinterprets every constraint in every saved recipe.
|
||||
EqualRadius,
|
||||
Collinear
|
||||
Collinear,
|
||||
DistanceX, // |dx| between two points, projected onto the sketch X axis
|
||||
DistanceY // |dy| between two points, projected onto the sketch Y axis
|
||||
};
|
||||
|
||||
// Constraint on a SketchProfile, referencing profile point indices (a,b,c,d).
|
||||
|
||||
@@ -73,6 +73,15 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
b.P(G_FIXED, qw), b.P(G_FIXED, qx), b.P(G_FIXED, qy), b.P(G_FIXED, qz)));
|
||||
b.wp = b.E(Slvs_MakeWorkplane(++b.eh, G_FIXED, origin, b.normal));
|
||||
|
||||
// Unit direction references for the axis-projected distance constraints. Both live in
|
||||
// G_FIXED, so they are held constant and add no DOF to the system.
|
||||
// libslvs defines a LINE_SEGMENT's direction as point[0] - point[1] (entity.cpp
|
||||
// VectorGetExprs), so the unit vector's head is listed first to yield +X / +Y.
|
||||
const Slvs_hEntity dir_x [[maybe_unused]] = b.E(Slvs_MakeLineSegment(++b.eh, G_FIXED, b.wp,
|
||||
b.pt2d(G_FIXED, 1.0, 0.0), b.pt2d(G_FIXED, 0.0, 0.0)));
|
||||
const Slvs_hEntity dir_y [[maybe_unused]] = b.E(Slvs_MakeLineSegment(++b.eh, G_FIXED, b.wp,
|
||||
b.pt2d(G_FIXED, 0.0, 1.0), b.pt2d(G_FIXED, 0.0, 0.0)));
|
||||
|
||||
// ---- Entities -------------------------------------------------------------------
|
||||
std::vector<Slots> slot(entities.size());
|
||||
for (size_t i = 0; i < entities.size(); ++i) {
|
||||
@@ -158,6 +167,9 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
switch (c.type) {
|
||||
case CT::Coincident: case CT::Horizontal: case CT::Vertical: case CT::Distance:
|
||||
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb); break;
|
||||
case CT::DistanceX:
|
||||
case CT::DistanceY:
|
||||
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb); break;
|
||||
case CT::Concentric:
|
||||
ref_ok = ptOf(c.ea, Role::Center) && ptOf(c.eb, Role::Center); break;
|
||||
case CT::Fix: case CT::LockX: case CT::LockY:
|
||||
@@ -194,6 +206,14 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
case CT::Distance:
|
||||
b.C(SLVS_C_PT_PT_DISTANCE, c.value, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
|
||||
break;
|
||||
case CT::DistanceX:
|
||||
// Distance between the two points measured along X only: project the vector
|
||||
// between them onto the fixed unit X direction.
|
||||
b.C(SLVS_C_PROJ_PT_DISTANCE, c.value, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), dir_x, 0);
|
||||
break;
|
||||
case CT::DistanceY:
|
||||
b.C(SLVS_C_PROJ_PT_DISTANCE, c.value, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), dir_y, 0);
|
||||
break;
|
||||
case CT::Fix: {
|
||||
const Vec2d p = coordOf(c.ea, c.ra);
|
||||
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, c.ra), fixedRef(p.x(), p.y()), 0, 0);
|
||||
|
||||
@@ -1530,6 +1530,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
cbtn("design_c_radius", _L("Radius"), SketchConstraintType::Radius);
|
||||
cbtn("design_c_diameter", _L("Diameter"), SketchConstraintType::Diameter);
|
||||
cbtn("design_c_fix", _L("Fix point (anchor in place)"), SketchConstraintType::Fix);
|
||||
cbtn("design_c_dist_x", _L("Horizontal distance"), SketchConstraintType::DistanceX);
|
||||
cbtn("design_c_dist_y", _L("Vertical distance"), SketchConstraintType::DistanceY);
|
||||
// Trim/Extend are now standalone SKETCH scissors (Mode::Trim/Extend) in the sketch
|
||||
// toolbar, NOT Constrain buttons. The other edit ops (Mirror/Offset/Fillet/Chamfer/
|
||||
// Move/…) are first-class sketch tools too. Done constraining = the action-bar ✓.
|
||||
@@ -7734,7 +7736,8 @@ void DesignPanel::apply_entity_constraint(SketchConstraintType type)
|
||||
type == T::Concentric || type == T::Tangent ||
|
||||
type == T::Angle || type == T::Midpoint ||
|
||||
type == T::Symmetric || type == T::EqualRadius ||
|
||||
type == T::Collinear);
|
||||
type == T::Collinear ||
|
||||
type == T::DistanceX || type == T::DistanceY);
|
||||
if (e0 < 0 || e0 >= int(feat.entities.size()) ||
|
||||
(needs_two && (e1 < 0 || e1 >= int(feat.entities.size())))) {
|
||||
fail(needs_two ? _L("Pick two entities first") : _L("Pick an entity first"));
|
||||
@@ -7772,6 +7775,41 @@ void DesignPanel::apply_entity_constraint(SketchConstraintType type)
|
||||
def.ea = e0; def.ra = ra; def.eb = e1; def.rb = rb;
|
||||
break;
|
||||
}
|
||||
case T::DistanceX:
|
||||
case T::DistanceY: {
|
||||
// Axis-projected distance between the closest endpoint pair of the two picked
|
||||
// entities. Typed in-canvas pre-filled with the current projection, committed on
|
||||
// the typed value (same deferred pattern as Angle).
|
||||
const SketchEntity& A = feat.entities[e0];
|
||||
const SketchEntity& B = feat.entities[e1];
|
||||
const std::pair<R, Vec2d> aps[2] = {{R::P0, A.p0}, {R::P1, A.p1}};
|
||||
const std::pair<R, Vec2d> bps[2] = {{R::P0, B.p0}, {R::P1, B.p1}};
|
||||
R ra = R::P1, rb = R::P0;
|
||||
Vec2d pa = A.p1, pb = B.p0;
|
||||
double best = 1e30;
|
||||
for (const auto& ap : aps)
|
||||
for (const auto& bp : bps) {
|
||||
const double d = (ap.second - bp.second).squaredNorm();
|
||||
if (d < best) { best = d; ra = ap.first; rb = bp.first; pa = ap.second; pb = bp.second; }
|
||||
}
|
||||
// The constraint is SIGNED: PROJ_PT_DISTANCE fixes (pB - pA).dot(axis), not its
|
||||
// magnitude. Showing |delta| while the current signed delta is negative would mean
|
||||
// that opening the dimension and simply accepting the number on screen flips the
|
||||
// point to the other side of its anchor. Opening a dimension and accepting its own
|
||||
// value must be a no-op, so order the two refs to make the shown value the positive
|
||||
// one -- which is also how a dimension ought to read.
|
||||
int a = e0, b = e1;
|
||||
double delta = (type == T::DistanceX) ? (pb.x() - pa.x()) : (pb.y() - pa.y());
|
||||
if (delta < 0.0) { std::swap(a, b); std::swap(ra, rb); delta = -delta; }
|
||||
const double cur = delta;
|
||||
const T tt = type;
|
||||
m_viewport->open_inline_value(cur, [this, a, b, ra, rb, tt](double v) {
|
||||
SketchEntityConstraintDef d;
|
||||
d.type = tt; d.ea = a; d.ra = ra; d.eb = b; d.rb = rb; d.value = v;
|
||||
commit_entity_constraint(d);
|
||||
});
|
||||
return; // deferred: commit runs on the typed value
|
||||
}
|
||||
case T::Concentric: {
|
||||
// Two circles/arcs: make their centres coincide.
|
||||
if (!is_round(feat.entities[e0]) || !is_round(feat.entities[e1])) {
|
||||
|
||||
@@ -222,3 +222,81 @@ TEST_CASE("slvs: collinear on already-collinear lines moves nothing", "[slvs][Ca
|
||||
CHECK(ents[i].p1.y() == Approx(before[i].p1.y()).margin(1e-9));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("slvs: distance-x drives the horizontal gap and leaves Y alone", "[slvs][CadDocument]")
|
||||
{
|
||||
std::vector<SketchEntity> ents = { line({0, 0}, {3, 7}) };
|
||||
std::vector<SketchEntityConstraintDef> cons = {
|
||||
con(CT::Fix, 0, R::P0, 0, R::P0),
|
||||
con(CT::DistanceX, 0, R::P0, 0, R::P1, 10.0),
|
||||
};
|
||||
auto res = sketch_solve(ents, cons);
|
||||
REQUIRE(res.ok);
|
||||
// SIGNED, not abs. PROJ_PT_DISTANCE constrains (pB - pA).dot(unit(dir)), and a
|
||||
// LINE_SEGMENT's direction is point[0] - point[1] (slvs entity.cpp), so the reference
|
||||
// line is built head-first to mean +X. Assert on abs and a flipped reference passes
|
||||
// while every dimension lands the point on the wrong side of its anchor.
|
||||
CHECK(ents[0].p1.x() - ents[0].p0.x() == Approx(10.0).margin(1e-9));
|
||||
CHECK(ents[0].p1.y() == Approx(7.0).margin(1e-9)); // Y must not be disturbed
|
||||
}
|
||||
|
||||
TEST_CASE("slvs: distance-y drives the vertical gap and leaves X alone", "[slvs][CadDocument]")
|
||||
{
|
||||
std::vector<SketchEntity> ents = { line({0, 0}, {3, 7}) };
|
||||
std::vector<SketchEntityConstraintDef> cons = {
|
||||
con(CT::Fix, 0, R::P0, 0, R::P0),
|
||||
con(CT::DistanceY, 0, R::P0, 0, R::P1, 10.0),
|
||||
};
|
||||
auto res = sketch_solve(ents, cons);
|
||||
REQUIRE(res.ok);
|
||||
CHECK(ents[0].p1.y() - ents[0].p0.y() == Approx(10.0).margin(1e-9)); // signed: see above
|
||||
CHECK(ents[0].p1.x() == Approx(3.0).margin(1e-9)); // X must not be disturbed
|
||||
}
|
||||
|
||||
TEST_CASE("slvs: distance-x is not the straight-line distance", "[slvs][CadDocument]")
|
||||
{
|
||||
// B is at straight-line distance 10 from A; DistanceX = 6 is already satisfied, so a
|
||||
// correct projection leaves B untouched. This is the case that fails if the constraint
|
||||
// were wired to SLVS_C_PT_PT_DISTANCE, which would drag B onto the radius-6 circle.
|
||||
std::vector<SketchEntity> ents = { line({0, 0}, {6, 8}) };
|
||||
std::vector<SketchEntityConstraintDef> cons = {
|
||||
con(CT::Fix, 0, R::P0, 0, R::P0),
|
||||
con(CT::DistanceX, 0, R::P0, 0, R::P1, 6.0),
|
||||
};
|
||||
auto res = sketch_solve(ents, cons);
|
||||
REQUIRE(res.ok);
|
||||
CHECK(ents[0].p1.x() == Approx(6.0).margin(1e-9));
|
||||
CHECK(ents[0].p1.y() == Approx(8.0).margin(1e-9));
|
||||
}
|
||||
|
||||
TEST_CASE("slvs: distance-x plus distance-y fully locates a point", "[slvs][CadDocument]")
|
||||
{
|
||||
std::vector<SketchEntity> ents = { line({0, 0}, {1, 1}) };
|
||||
std::vector<SketchEntityConstraintDef> cons = {
|
||||
con(CT::Fix, 0, R::P0, 0, R::P0),
|
||||
con(CT::DistanceX, 0, R::P0, 0, R::P1, 4.0),
|
||||
con(CT::DistanceY, 0, R::P0, 0, R::P1, 3.0),
|
||||
};
|
||||
auto res = sketch_solve(ents, cons);
|
||||
REQUIRE(res.ok);
|
||||
CHECK(ents[0].p1.x() - ents[0].p0.x() == Approx(4.0).margin(1e-9)); // signed: see above
|
||||
CHECK(ents[0].p1.y() - ents[0].p0.y() == Approx(3.0).margin(1e-9));
|
||||
}
|
||||
|
||||
// The property the GUI's ref-ordering exists to preserve: DistanceX is SIGNED, so applying
|
||||
// the CURRENT projected delta as the target must not move anything. If the refs are ordered
|
||||
// so the shown value is positive while the actual signed delta is negative, accepting the
|
||||
// value a dimension opens with teleports the point to the other side of its anchor.
|
||||
TEST_CASE("slvs: applying a point's own distance-x is a no-op", "[slvs][CadDocument]")
|
||||
{
|
||||
// p1 sits to the LEFT of p0, so the signed delta p1 - p0 is negative.
|
||||
std::vector<SketchEntity> ents = { line({0, 0}, {-4, 7}) };
|
||||
std::vector<SketchEntityConstraintDef> cons = {
|
||||
con(CT::Fix, 0, R::P0, 0, R::P0),
|
||||
con(CT::DistanceX, 0, R::P0, 0, R::P1, -4.0), // the CURRENT signed delta
|
||||
};
|
||||
auto res = sketch_solve(ents, cons);
|
||||
REQUIRE(res.ok);
|
||||
CHECK(ents[0].p1.x() == Approx(-4.0).margin(1e-9)); // stayed left, did not flip to +4
|
||||
CHECK(ents[0].p1.y() == Approx(7.0).margin(1e-9));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user