Ladder rung 9: grade the engine against 50 real drawings, not against my taste

Ported from snaporca 5b82c846f3.
This commit is contained in:
Tommaso Bianchi
2026-08-22 23:28:36 +02:00
parent 1274d97983
commit 05ce2607a8
4 changed files with 391 additions and 6 deletions
+11 -3
View File
@@ -2311,7 +2311,7 @@ bool DesignSketchTool::try_add_constraints(const std::vector<SketchEntityConstra
return false;
}
void DesignSketchTool::infer_auto_constraints(int base)
void DesignSketchTool::infer_auto_constraints(int base, double ang_tol_rad)
{
const int n = int(m_entities.size());
if (base < 0 || base >= n) return;
@@ -2357,7 +2357,7 @@ void DesignSketchTool::infer_auto_constraints(int base)
// so a single conflict never drops the others).
for (int i = base; i < n; ++i) {
if (m_entities[i].type != SketchEntity::Type::Line) continue;
auto ax = infer_axis_constraint(m_entities[i].p0, m_entities[i].p1);
auto ax = infer_axis_constraint(m_entities[i].p0, m_entities[i].p1, ang_tol_rad);
if (!ax) continue;
SketchEntityConstraintDef c;
c.type = *ax;
@@ -8896,7 +8896,15 @@ int DesignSketchTool::add_entities_scripted(const std::vector<SketchEntity>& ent
if (ents.empty()) return -1;
const int base = int(m_entities.size());
for (const SketchEntity& e : ents) m_entities.push_back(e);
infer_auto_constraints(base); // the same auto-coincidence/H/V pass a gesture runs
// Auto-constrain, but do NOT let the inference move what the caller specified. A gesture
// gets 3 degrees of slack because a hand cannot click an exact horizontal; a scripted add
// has already said exactly what it means, and snapping a segment 2 degrees off to exactly
// horizontal silently rewrites it. Measured on a real drawing: feeding the 64 flattened
// segments of one circle moved vertices by up to 0.058 mm and shrank the enclosed area by
// 0.067%, because several segments of the polygon fell inside that 3 degree window. Exact
// coincidence inference is unaffected — it already tests to 1e-6 — so chains still weld
// and genuinely axis-aligned scripted geometry still gets its Horizontal/Vertical.
infer_auto_constraints(base, 1e-4);
resolve_live();
return base;
}
+5 -1
View File
@@ -628,7 +628,11 @@ private:
// After entities [base, end) were committed, auto-emit the constraints that make
// the new geometry stick: Coincident between co-located endpoints (so loops close
// on their own) and Horizontal/Vertical on axis-aligned new segments.
void infer_auto_constraints(int base);
// ang_tol_rad is how far from an axis a segment may be and still be CALLED axis-aligned.
// A gesture needs the default 3 degrees — nobody clicks a horizontal line exactly — but
// that same slack MOVES geometry that was given exactly, so the scripted path passes a
// tolerance tight enough to recognise only what is already true. See add_entities_scripted.
void infer_auto_constraints(int base, double ang_tol_rad = 3.0 * M_PI / 180.0);
// Selection helpers (Mode::Select).
int hit_test(const Vec2d& p, double tol) const; // nearest entity within tol, or -1
+14 -2
View File
@@ -1204,18 +1204,30 @@ SketchEntity sketch_entity_from(const json& j)
return Vec2d(a[0].get<double>(), a[1].get<double>());
};
e.construction = j.value("construction", false);
// A circle and an arc are defined BY their centre, so a request that does not carry one is
// incomplete, not a request for a circle at the origin. Defaulting it silently put geometry
// somewhere the caller never asked for and then reported perfectly consistent loops, areas
// and hole attribution ABOUT THAT WRONG GEOMETRY — which is far more expensive to disbelieve
// than an error would have been. `p0` is accepted as an alias because that is exactly what a
// circle stores internally (e.p0 = e.center below), so a caller who writes p0 means centre.
auto centre_of = [&](const char* what) {
if (j.contains("center")) return p("center", 0, 0);
if (j.contains("centre")) return p("centre", 0, 0);
if (j.contains("p0")) return p("p0", 0, 0);
throw std::runtime_error(std::string(what) + " needs a 'center' (or 'p0')");
};
if (t == "line") {
e.type = SketchEntity::Type::Line;
e.p0 = p("p0", 0, 0); e.p1 = p("p1", 0, 0);
} else if (t == "circle") {
e.type = SketchEntity::Type::Circle;
e.center = p("center", 0, 0);
e.center = centre_of("circle");
e.radius = j.value("radius", 0.0);
e.p0 = e.center;
if (e.radius <= 0.0) throw std::runtime_error("circle needs a positive 'radius'");
} else if (t == "arc") {
e.type = SketchEntity::Type::Arc;
e.center = p("center", 0, 0);
e.center = centre_of("arc");
e.radius = j.value("radius", 0.0);
e.start_angle = j.value("start_angle", 0.0);
e.end_angle = j.value("end_angle", 0.0);