System information
- slvs Python package 3.2 (PyPI wheel), Linux x86_64, Python 3.13
- Code inspected: master, src/slvs/lib.cpp and src/constrainteq.cpp
Expected behavior
Solving a sketch whose constraints are already satisfied should leave the geometry where it is. A point that already lies on a line should stay there, and so should the line.
Actual behavior
With the sketch solving API (clear_sketch / add_* / solve_sketch), a satisfied PT_ON_LINE constraint moves the geometry. PT_ON_LINE gets an auxiliary parameter t (ptOnLine = a + t*(b - a)), and the library creates it with t = 0 and never adjusts it. So the solver treats the point as if it had to sit on the line's start point, and the Newton step spreads the correction over the point and both line endpoints.
Applications that rebuild the system before every solve (e.g. the CAD Sketcher Blender add-on) see the geometry creep on every solve. Tangent constructions built from a helper point on a line drift the same way.
Steps to reproduce
import slvs
slvs.clear_sketch()
g0, g = 1, 3
o = slvs.add_point_3d(g0, 0, 0, 0)
n = slvs.add_normal_3d(g0, 1, 0, 0, 0)
wp = slvs.add_workplane(g0, o, n)
a = slvs.add_point_2d(g, 0.0, 0.0, wp)
b = slvs.add_point_2d(g, 5.0, 1.0, wp)
p = slvs.add_point_2d(g, 2.5, 0.5, wp) # exactly on line a-b (t = 0.5)
line = slvs.add_line_2d(g, a, b, wp)
slvs.coincident(g, p, line, wp) # -> PT_ON_LINE
print(slvs.solve_sketch(g, True))
print([round(slvs.get_param_value(h), 5) for e in (a, b, p) for h in e["param"][:2]])
Output:
({'result': 0, 'dof': 5, 'nbad': 0}, [])
[0.09012, 0.01802, 5.00072, 1.00014, 2.40915, 0.48183]
Expected [0.0, 0.0, 5.0, 1.0, 2.5, 0.5]. If p is placed at a (t = 0) nothing moves, which points at the uninitialized t. For comparison, PT_LINE_DISTANCE with value 0 (no auxiliary parameter) leaves the same geometry untouched, and so does PT_ON_CIRCLE.
Cause
Slvs_SolveSketch calls ModifyToSatisfy() on a freshly generated constraint parameter only if Slvs_CanInitiallySatisfy() returns true, and PT_ON_LINE is in the false group:
case ConstraintBase::Type::POINTS_COINCIDENT:
case ConstraintBase::Type::PT_ON_LINE:
case ConstraintBase::Type::SYMMETRIC:
...
// Can't initially satisfy these constraints
return false;
That list was added in cbcc5f5 (#1562) to avoid the single-equation assertion in the generic branch of ModifyToSatisfy(). PT_ON_LINE never reaches that branch, though: ModifyToSatisfy() has a dedicated case that sets t by projecting the point onto the line (constrainteq.cpp, else if(type == Type::PT_ON_LINE)). Before #1562 the library called ModifyToSatisfy() unconditionally, so this looks like a regression.
Suggested fix
Move PT_ON_LINE to the return true group in Slvs_CanInitiallySatisfy(), so its t is set from the current geometry.
System information
Expected behavior
Solving a sketch whose constraints are already satisfied should leave the geometry where it is. A point that already lies on a line should stay there, and so should the line.
Actual behavior
With the sketch solving API (clear_sketch / add_* / solve_sketch), a satisfied PT_ON_LINE constraint moves the geometry. PT_ON_LINE gets an auxiliary parameter t (ptOnLine = a + t*(b - a)), and the library creates it with t = 0 and never adjusts it. So the solver treats the point as if it had to sit on the line's start point, and the Newton step spreads the correction over the point and both line endpoints.
Applications that rebuild the system before every solve (e.g. the CAD Sketcher Blender add-on) see the geometry creep on every solve. Tangent constructions built from a helper point on a line drift the same way.
Steps to reproduce
Output:
({'result': 0, 'dof': 5, 'nbad': 0}, []) [0.09012, 0.01802, 5.00072, 1.00014, 2.40915, 0.48183]Expected [0.0, 0.0, 5.0, 1.0, 2.5, 0.5]. If p is placed at a (t = 0) nothing moves, which points at the uninitialized t. For comparison, PT_LINE_DISTANCE with value 0 (no auxiliary parameter) leaves the same geometry untouched, and so does PT_ON_CIRCLE.
Cause
Slvs_SolveSketch calls ModifyToSatisfy() on a freshly generated constraint parameter only if Slvs_CanInitiallySatisfy() returns true, and PT_ON_LINE is in the false group:
That list was added in cbcc5f5 (#1562) to avoid the single-equation assertion in the generic branch of ModifyToSatisfy(). PT_ON_LINE never reaches that branch, though: ModifyToSatisfy() has a dedicated case that sets t by projecting the point onto the line (constrainteq.cpp, else if(type == Type::PT_ON_LINE)). Before #1562 the library called ModifyToSatisfy() unconditionally, so this looks like a regression.
Suggested fix
Move PT_ON_LINE to the return true group in Slvs_CanInitiallySatisfy(), so its t is set from the current geometry.