mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Equal radius and Collinear, and one Equal button that knows what it picked
Port of snaporca 9ec6405e2d. Parity OK: 17 files identical, 8 diverging at their
expected counts (DesignPanel.cpp 32, test_slvs_constraints.cpp 3).
Measured on the CAD-1000-hours corpus: 51.5% of observed CAD time is 2D sketch
work, and dimensioning/constraining alone is 31.9% -- the largest single class.
Two constraints every industrial sketcher has were missing here.
EqualRadius fixes a dead end rather than adding a feature. Picking two circles
and pressing Equal emitted EqualLength, which maps to SLVS_C_EQUAL_LENGTH_LINES
and constrains nothing on a curve: a silent no-op with no error. Equal is now
one button with two meanings, as in Onshape and SolidWorks.
Collinear emits PARALLEL plus PT_LINE_DISTANCE=0 rather than PT_ON_LINE, whose
internal valP param this libslvs port leaves at 0, drifting an already-collinear
pair.
Both types are appended at the END of SketchConstraintType: cereal serializes it
positionally, so inserting elsewhere reinterprets every saved recipe.
VERIFICATION LIMIT, stated rather than implied: this fork's kernel suite could
NOT be run. scripts/CAD/run-kernel-tests.sh fails at CMake configure time on
find_package(assimp), before any source compiles -- a pre-existing deps gap
(snaporca-w80c), not this change. The shared sources are byte-identical to
snaporca's, where the full gate passed: kernel 2588/195 and ALL LADDERS HELD
across all seven rungs.
Also fixes two defects in this fork's scripts/CAD/run-all-checks.sh:
- `cd $(dirname $0)/..` landed in scripts/ instead of the repo root, so every
rung looked for itself under scripts/scripts/. Broken since the script moved
into scripts/CAD/; the three sibling scripts were fixed then and this was
missed, so the gate has not run since.
- C defaulted to snaporca-gui, the OTHER fork's rig container, so this fork's
gate would drive snaporca's app and report green about the wrong binary.
run-kernel-tests.sh:31 documents the identical defect being fixed once
already for the build volume; this is the third instance.
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="M2 12h20" stroke-dasharray="1.5 1.5"/><path d="M5 12h5M14 12h5"/></svg>
|
||||||
|
After Width: | Height: | Size: 260 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"><circle cx="4" cy="12" r="2.5"/><circle cx="15" cy="12" r="4.5"/><path d="M8 10h2M8 14h2"/></svg>
|
||||||
|
After Width: | Height: | Size: 277 B |
@@ -13,9 +13,16 @@
|
|||||||
# The rig container is expected to be up with the app running and SNAPORCA_MCP set; bring it up
|
# The rig container is expected to be up with the app running and SNAPORCA_MCP set; bring it up
|
||||||
# with scripts/CAD/start-headless-gui.sh inside it. The corpus lives at /corpus in that container.
|
# with scripts/CAD/start-headless-gui.sh inside it. The corpus lives at /corpus in that container.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
cd "$(dirname "$0")/.." || exit 1
|
# ../.. -- this script lives in scripts/CAD/, so one level up is scripts/, not the repo
|
||||||
|
# root. It was scripts/ladder-all.sh when it was written; the move fixed the three sibling
|
||||||
|
# scripts and missed this one, which left every rung looking for its own path under
|
||||||
|
# scripts/scripts/ and reporting instant failures that were all the same typo.
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 1
|
||||||
|
|
||||||
C="${C:-snaporca-gui}"
|
# orcacad-gui, NOT snaporca-gui: that is the other fork's rig, and defaulting to it makes
|
||||||
|
# this gate verify the wrong fork's binary while reporting green. run-kernel-tests.sh
|
||||||
|
# carries the same warning about the build volume, where the defect was found first.
|
||||||
|
C="${C:-orcacad-gui}"
|
||||||
CORPUS="${CORPUS:-/corpus}"
|
CORPUS="${CORPUS:-/corpus}"
|
||||||
STEP="${STEP:-20}"
|
STEP="${STEP:-20}"
|
||||||
[ -n "${FULL:-}" ] && STEP=1
|
[ -n "${FULL:-}" ] && STEP=1
|
||||||
|
|||||||
@@ -84,7 +84,11 @@ enum class SketchConstraintType {
|
|||||||
Tangent, Midpoint, Symmetric, Angle,
|
Tangent, Midpoint, Symmetric, Angle,
|
||||||
Radius, Diameter,
|
Radius, Diameter,
|
||||||
PointOnLine, // a point lies on a line (or at signed perpendicular distance `value`)
|
PointOnLine, // a point lies on a line (or at signed perpendicular distance `value`)
|
||||||
PointOnObject // a point lies on an entity edge (line -> PT_ON_LINE, circle -> PT_ON_CIRCLE)
|
PointOnObject, // a point lies on an entity edge (line -> PT_ON_LINE, circle -> PT_ON_CIRCLE)
|
||||||
|
// 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
|
||||||
};
|
};
|
||||||
|
|
||||||
// Constraint on a SketchProfile, referencing profile point indices (a,b,c,d).
|
// Constraint on a SketchProfile, referencing profile point indices (a,b,c,d).
|
||||||
|
|||||||
@@ -173,6 +173,9 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
|||||||
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb) && primOf(c.ec); break;
|
ref_ok = ptOf(c.ea, c.ra) && ptOf(c.eb, c.rb) && primOf(c.ec); break;
|
||||||
case CT::PointOnLine: case CT::PointOnObject:
|
case CT::PointOnLine: case CT::PointOnObject:
|
||||||
ref_ok = ptOf(c.ea, c.ra) && primOf(c.eb); break;
|
ref_ok = ptOf(c.ea, c.ra) && primOf(c.eb); break;
|
||||||
|
case CT::EqualRadius:
|
||||||
|
case CT::Collinear:
|
||||||
|
ref_ok = primOf(c.ea) && primOf(c.eb); break;
|
||||||
}
|
}
|
||||||
if (!ref_ok) continue;
|
if (!ref_ok) continue;
|
||||||
switch (c.type) {
|
switch (c.type) {
|
||||||
@@ -280,6 +283,20 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
|||||||
else
|
else
|
||||||
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
||||||
break;
|
break;
|
||||||
|
case CT::EqualRadius:
|
||||||
|
b.C(SLVS_C_EQUAL_RADIUS, 0, 0, 0, primOf(c.ea), primOf(c.eb));
|
||||||
|
break;
|
||||||
|
case CT::Collinear:
|
||||||
|
// libslvs has no collinear code. Two lines are collinear iff they are parallel
|
||||||
|
// AND a point of one lies on the other's infinite line — emit both.
|
||||||
|
b.C(SLVS_C_PARALLEL, 0, 0, 0, primOf(c.ea), primOf(c.eb));
|
||||||
|
// Point-on-infinite-line via PT_LINE_DISTANCE=0 rather than PT_ON_LINE: the
|
||||||
|
// latter creates an internal `valP` param that this port's Slvs_Solve leaves at
|
||||||
|
// 0 in the working set (ModifyToSatisfy only updates SK.param), so an already
|
||||||
|
// collinear pair drifts. PT_LINE_DISTANCE=0 is the same condition with no extra
|
||||||
|
// parameter, so an already-satisfied solve is a clean no-op.
|
||||||
|
b.C(SLVS_C_PT_LINE_DISTANCE, 0, ptOf(c.eb, Role::P0), 0, primOf(c.ea), 0);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1520,6 +1520,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
|||||||
cbtn("design_c_perpendicular", _L("Perpendicular"), SketchConstraintType::Perpendicular);
|
cbtn("design_c_perpendicular", _L("Perpendicular"), SketchConstraintType::Perpendicular);
|
||||||
cbtn("design_c_coincident", _L("Coincident"), SketchConstraintType::Coincident);
|
cbtn("design_c_coincident", _L("Coincident"), SketchConstraintType::Coincident);
|
||||||
cbtn("design_c_equal", _L("Equal length"), SketchConstraintType::EqualLength);
|
cbtn("design_c_equal", _L("Equal length"), SketchConstraintType::EqualLength);
|
||||||
|
cbtn("design_c_equal_radius", _L("Equal radius"), SketchConstraintType::EqualRadius);
|
||||||
|
cbtn("design_c_collinear", _L("Collinear"), SketchConstraintType::Collinear);
|
||||||
cbtn("design_c_concentric", _L("Concentric"), SketchConstraintType::Concentric);
|
cbtn("design_c_concentric", _L("Concentric"), SketchConstraintType::Concentric);
|
||||||
cbtn("design_c_tangent", _L("Tangent"), SketchConstraintType::Tangent);
|
cbtn("design_c_tangent", _L("Tangent"), SketchConstraintType::Tangent);
|
||||||
cbtn("design_c_midpoint", _L("Midpoint"), SketchConstraintType::Midpoint);
|
cbtn("design_c_midpoint", _L("Midpoint"), SketchConstraintType::Midpoint);
|
||||||
@@ -7717,18 +7719,27 @@ void DesignPanel::apply_entity_constraint(SketchConstraintType type)
|
|||||||
};
|
};
|
||||||
|
|
||||||
CadFeature& feat = m_doc.features[m_constrain_feat];
|
CadFeature& feat = m_doc.features[m_constrain_feat];
|
||||||
|
|
||||||
|
auto is_round = [](const SketchEntity& e) {
|
||||||
|
return e.type == SketchEntity::Type::Circle || e.type == SketchEntity::Type::Arc; };
|
||||||
|
|
||||||
|
// One Equal button, two meanings: lines get equal length, curves equal radius.
|
||||||
|
if (type == T::EqualLength && e0 >= 0 && e1 >= 0 &&
|
||||||
|
e0 < int(feat.entities.size()) && e1 < int(feat.entities.size()) &&
|
||||||
|
is_round(feat.entities[e0]) && is_round(feat.entities[e1]))
|
||||||
|
type = T::EqualRadius;
|
||||||
|
|
||||||
const bool needs_two = (type == T::Parallel || type == T::Perpendicular ||
|
const bool needs_two = (type == T::Parallel || type == T::Perpendicular ||
|
||||||
type == T::EqualLength || type == T::Coincident ||
|
type == T::EqualLength || type == T::Coincident ||
|
||||||
type == T::Concentric || type == T::Tangent ||
|
type == T::Concentric || type == T::Tangent ||
|
||||||
type == T::Angle || type == T::Midpoint ||
|
type == T::Angle || type == T::Midpoint ||
|
||||||
type == T::Symmetric);
|
type == T::Symmetric || type == T::EqualRadius ||
|
||||||
|
type == T::Collinear);
|
||||||
if (e0 < 0 || e0 >= int(feat.entities.size()) ||
|
if (e0 < 0 || e0 >= int(feat.entities.size()) ||
|
||||||
(needs_two && (e1 < 0 || e1 >= 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"));
|
fail(needs_two ? _L("Pick two entities first") : _L("Pick an entity first"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto is_round = [](const SketchEntity& e) {
|
|
||||||
return e.type == SketchEntity::Type::Circle || e.type == SketchEntity::Type::Arc; };
|
|
||||||
|
|
||||||
SketchEntityConstraintDef def;
|
SketchEntityConstraintDef def;
|
||||||
def.type = type;
|
def.type = type;
|
||||||
@@ -7832,6 +7843,21 @@ void DesignPanel::apply_entity_constraint(SketchConstraintType type)
|
|||||||
commit_entity_constraints(defs);
|
commit_entity_constraints(defs);
|
||||||
return; // multi-def commit done here
|
return; // multi-def commit done here
|
||||||
}
|
}
|
||||||
|
case T::EqualRadius: {
|
||||||
|
if (!is_round(feat.entities[e0]) || !is_round(feat.entities[e1])) {
|
||||||
|
fail(_L("Equal radius needs two circles or arcs")); return;
|
||||||
|
}
|
||||||
|
def.ea = e0; def.eb = e1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case T::Collinear: {
|
||||||
|
using ET = SketchEntity::Type;
|
||||||
|
if (feat.entities[e0].type != ET::Line || feat.entities[e1].type != ET::Line) {
|
||||||
|
fail(_L("Collinear needs two lines")); return;
|
||||||
|
}
|
||||||
|
def.ea = e0; def.eb = e1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case T::Fix: {
|
case T::Fix: {
|
||||||
// Anchor the picked entity's reference point to its current coordinate (the
|
// Anchor the picked entity's reference point to its current coordinate (the
|
||||||
// kernel pins it to a fixed reference). A single point — not both endpoints —
|
// kernel pins it to a fixed reference). A single point — not both endpoints —
|
||||||
|
|||||||
@@ -159,3 +159,66 @@ TEST_CASE("slvs: a sketch past the solver's unknown limit still solves", "[slvs]
|
|||||||
auto res3 = sketch_solve(ents, cons);
|
auto res3 = sketch_solve(ents, cons);
|
||||||
CHECK_FALSE(res3.ok);
|
CHECK_FALSE(res3.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("slvs: equal radius drives two circles to one radius", "[slvs][CadDocument]")
|
||||||
|
{
|
||||||
|
std::vector<SketchEntity> ents = { circle({0, 0}, 5.0), circle({10, 0}, 12.0) };
|
||||||
|
std::vector<SketchEntityConstraintDef> cons = {
|
||||||
|
con(CT::EqualRadius, 0, R::P0, 1, R::P0),
|
||||||
|
};
|
||||||
|
auto res = sketch_solve(ents, cons);
|
||||||
|
REQUIRE(res.ok);
|
||||||
|
CHECK(ents[0].radius == Approx(ents[1].radius).margin(1e-9));
|
||||||
|
CHECK(ents[0].radius > 1e-6); // equal-at-zero would satisfy the line above trivially
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("slvs: equal radius plus a radius dimension pins both", "[slvs][CadDocument]")
|
||||||
|
{
|
||||||
|
std::vector<SketchEntity> ents = { circle({0, 0}, 5.0), circle({10, 0}, 12.0) };
|
||||||
|
std::vector<SketchEntityConstraintDef> cons = {
|
||||||
|
con(CT::EqualRadius, 0, R::P0, 1, R::P0),
|
||||||
|
con(CT::Radius, 0, R::P0, -1, R::P0, 8.0),
|
||||||
|
};
|
||||||
|
auto res = sketch_solve(ents, cons);
|
||||||
|
REQUIRE(res.ok);
|
||||||
|
CHECK(ents[0].radius == Approx(8.0).margin(1e-9));
|
||||||
|
CHECK(ents[1].radius == Approx(8.0).margin(1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("slvs: collinear makes two offset lines share one line", "[slvs][CadDocument]")
|
||||||
|
{
|
||||||
|
std::vector<SketchEntity> ents = { line({0, 0}, {10, 0}), line({0, 4}, {10, 4}) };
|
||||||
|
std::vector<SketchEntityConstraintDef> cons = {
|
||||||
|
con(CT::Collinear, 0, R::P0, 1, R::P0),
|
||||||
|
};
|
||||||
|
auto res = sketch_solve(ents, cons);
|
||||||
|
REQUIRE(res.ok);
|
||||||
|
const Vec2d& a0 = ents[0].p0;
|
||||||
|
const Vec2d ad = ents[0].p1 - ents[0].p0;
|
||||||
|
for (int k = 0; k <= 1; ++k) {
|
||||||
|
const Vec2d& pk = (k == 0) ? ents[1].p0 : ents[1].p1;
|
||||||
|
const double cross = ad.x() * (pk.y() - a0.y()) - ad.y() * (pk.x() - a0.x());
|
||||||
|
CHECK(cross == Approx(0.0).margin(1e-9));
|
||||||
|
}
|
||||||
|
// A line collapsed to a point is trivially collinear with anything, so the cross
|
||||||
|
// products above would pass on a degenerate solve. Both lines must survive intact.
|
||||||
|
CHECK(ad.norm() == Approx(10.0).margin(1e-9));
|
||||||
|
CHECK((ents[1].p1 - ents[1].p0).norm() == Approx(10.0).margin(1e-9));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("slvs: collinear on already-collinear lines moves nothing", "[slvs][CadDocument]")
|
||||||
|
{
|
||||||
|
std::vector<SketchEntity> ents = { line({0, 0}, {10, 0}), line({20, 0}, {30, 0}) };
|
||||||
|
std::vector<SketchEntityConstraintDef> cons = {
|
||||||
|
con(CT::Collinear, 0, R::P0, 1, R::P0),
|
||||||
|
};
|
||||||
|
std::vector<SketchEntity> before = ents;
|
||||||
|
auto res = sketch_solve(ents, cons);
|
||||||
|
REQUIRE(res.ok);
|
||||||
|
for (size_t i = 0; i < ents.size(); ++i) { // already satisfied: nothing may move
|
||||||
|
CHECK(ents[i].p0.x() == Approx(before[i].p0.x()).margin(1e-9));
|
||||||
|
CHECK(ents[i].p0.y() == Approx(before[i].p0.y()).margin(1e-9));
|
||||||
|
CHECK(ents[i].p1.x() == Approx(before[i].p1.x()).margin(1e-9));
|
||||||
|
CHECK(ents[i].p1.y() == Approx(before[i].p1.y()).margin(1e-9));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user