From f3595bce0b6f25bd8c369c1ef2872f648cd8fbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20B=C3=A4hr?= Date: Sun, 16 Apr 2023 23:04:11 +0200 Subject: [PATCH] Sketcher: Add tests for Circle to Line distance constraint Note that support for secants currently only works for reference constraints but not for driving ones. If and how this should be done is still under discussion [1]. [1]: https://github.com/FreeCAD/FreeCAD/pull/9044#issuecomment-1548006842 --- .../SketcherTests/TestSketcherSolver.py | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py b/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py index 8b34f0d542..b50a94aa76 100644 --- a/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py +++ b/src/Mod/Sketcher/SketcherTests/TestSketcherSolver.py @@ -22,8 +22,15 @@ import FreeCAD, os, sys, unittest, Part, Sketcher +from Part import Precision App = FreeCAD +xy_normal = FreeCAD.Vector(0, 0, 1) + +def vec(x, y): + """Shorthand to create a vector in the XY-plane""" + return FreeCAD.Vector(x, y, 0) + def CreateRectangleSketch(SketchFeature, corner, lengths): hmin, hmax = corner[0], corner[0] + lengths[0] vmin, vmax = corner[1], corner[1] + lengths[1] @@ -268,6 +275,69 @@ class TestSketcherSolver(unittest.TestCase): sketch.addConstraint(Sketcher.Constraint('Coincident',2,1,1,2)) self.assertEqual(sketch.detectMissingPointOnPointConstraints(0.0001), 0) + def testCircleToLineDistance_Driving_Passant(self): + sketch = self.Doc.addObject('Sketcher::SketchObject','Sketch') + radius = 20 + circle = Part.Circle(vec(0, 0), xy_normal, radius) + line = Part.LineSegment(vec(-radius, 2*radius), vec(radius, 2*radius)) + c_idx = sketch.addGeometry(circle) + l_idx = sketch.addGeometry(line) + # use a positive distance, other than the initial distance of the line + wanted_distance = radius/2 + sketch.addConstraint(Sketcher.Constraint('Distance', c_idx, l_idx, wanted_distance)) + self.assertSuccessfulSolve(sketch) + c_shape = sketch.Geometry[c_idx].toShape() + l_shape = sketch.Geometry[l_idx].toShape() + self.assertShapeDistance(c_shape, l_shape, wanted_distance) + + @unittest.skip("Support for secants still under discussion, see comments in PR 9044") + def testCircleToLineDistance_Driving_Secant(self): + sketch = self.Doc.addObject('Sketcher::SketchObject','Sketch') + radius = 20 + c_idx = sketch.addGeometry(Part.Circle(vec(0, 0), xy_normal, radius)) + l_idx = sketch.addGeometry(Part.LineSegment(vec(-radius, 2*radius), vec(radius, 2*radius))) + # use a negative distance to tell "line is within the circle" + wanted_distance = -radius/2 + sketch.addConstraint(Sketcher.Constraint('Distance', c_idx, l_idx, wanted_distance)) + self.assertSuccessfulSolve(sketch) + c_shape = sketch.Geometry[c_idx].toShape() + l_shape = sketch.Geometry[l_idx].toShape() + self.assertShapeDistance(c_shape, l_shape, 0) # secant intersects circle, thus no distance + + def testCircleToLineDistance_Reference_Secant(self): + sketch = self.Doc.addObject('Sketcher::SketchObject','Sketch') + radius = 20 + c_idx = sketch.addGeometry(Part.Circle(vec(0, 0), xy_normal, radius)) + l_idx = sketch.addGeometry(Part.LineSegment(vec(-radius, radius/2), vec(radius, radius/2))) + # The block constraints are required to ensure the geometry does not move. + # Without this, the solver may find another valid solution than what we assert. + sketch.addConstraint([ + Sketcher.Constraint('Block', c_idx), + Sketcher.Constraint('Block', l_idx)]) + # use a negative distance to tell "line is within the circle" + expected_distance = -radius/2 # note that we don't set this in the constraint below! + # TODO: addConstraint(constraint) triggers a solve (for godd reasons) however, this way + # one cannot add non-driving constraints. In contrast, addConstraint(list(constraint)) + # does not solve automatically, thus we use this "overload". + # Much nicer would be an addConstraint(constraint, isReference=False), like addGeometry + dist_idx = sketch.addConstraint([Sketcher.Constraint('Distance', c_idx, l_idx, 0)])[0] + sketch.setDriving(dist_idx, False) + self.assertSuccessfulSolve(sketch) + actual_distance = sketch.Constraints[dist_idx].Value + self.assertAlmostEqual(expected_distance, actual_distance, delta=Precision.confusion(), + msg="Reference constraint did not return the expected distance.") + + def assertSuccessfulSolve(self, sketch, msg=None): + status = sketch.solve() + # TODO: can we get the solver's messages somehow to improve the message? + self.failUnless(status == 0, msg=msg or "solver didn't converge") + + def assertShapeDistance(self, shape1, shape2, expected_distance, msg=None): + distance, _, _ = shape1.distToShape(shape2) + self.assertAlmostEqual(distance, expected_distance, + delta=Precision.confusion(), + msg=msg or "The given shapes are not spaced by the expected distance.") + def tearDown(self): #closing doc FreeCAD.closeDocument("SketchSolverTest")