Handle constraints from infinite faces (#797)

* Face.toPln method added

* solver: set xtol_abs

* Constraint: handle infinite faces

Infinite faces have their center at 1e99, which was causing overflows in
the solver and also was not what the user intended when creating the
Shape. They are now converted to the expected values.
This commit is contained in:
Marcus Boyd
2021-07-06 16:47:36 +09:30
committed by GitHub
parent 3cb72378b8
commit 6a440b63af
5 changed files with 127 additions and 22 deletions

View File

@ -71,6 +71,15 @@ def box_and_vertex():
return assy
def solve_result_check(solve_result: dict) -> bool:
checks = [
solve_result["status"] == nlopt.XTOL_REACHED,
solve_result["cost"] < 1e-9,
solve_result["iters"] > 0,
]
return all(checks)
def test_color():
c1 = cq.Color("red")
@ -228,9 +237,7 @@ def test_constrain(simple_assy, nested_assy):
simple_assy.solve()
assert simple_assy._solve_result["status"] == nlopt.XTOL_REACHED
assert simple_assy._solve_result["cost"] < 1e-9
assert simple_assy._solve_result["iters"] > 0
assert solve_result_check(simple_assy._solve_result)
assert (
simple_assy.loc.wrapped.Transformation()
@ -247,9 +254,7 @@ def test_constrain(simple_assy, nested_assy):
nested_assy.solve()
assert nested_assy._solve_result["status"] == nlopt.XTOL_REACHED
assert nested_assy._solve_result["cost"] < 1e-9
assert nested_assy._solve_result["iters"] > 0
assert solve_result_check(nested_assy._solve_result)
assert (
nested_assy.children[0]
@ -302,6 +307,7 @@ def test_PointInPlane_constraint(box_and_vertex):
param=0,
)
box_and_vertex.solve()
solve_result_check(box_and_vertex._solve_result)
x_pos = (
box_and_vertex.children[0].loc.wrapped.Transformation().TranslationPart().X()
@ -311,6 +317,7 @@ def test_PointInPlane_constraint(box_and_vertex):
# add a second PointInPlane constraint
box_and_vertex.constrain("vertex", "box@faces@>Y", "PointInPlane", param=0)
box_and_vertex.solve()
solve_result_check(box_and_vertex._solve_result)
vertex_translation_part = (
box_and_vertex.children[0].loc.wrapped.Transformation().TranslationPart()
@ -323,6 +330,7 @@ def test_PointInPlane_constraint(box_and_vertex):
# add a third PointInPlane constraint
box_and_vertex.constrain("vertex", "box@faces@>Z", "PointInPlane", param=0)
box_and_vertex.solve()
solve_result_check(box_and_vertex._solve_result)
# should now be on the >X and >Y and >Z corner
assert (
@ -342,6 +350,7 @@ def test_PointInPlane_3_parts(box_and_vertex):
box_and_vertex.constrain("vertex", "cylinder@faces@>Z", "PointInPlane")
box_and_vertex.constrain("vertex", "box@faces@>X", "PointInPlane")
box_and_vertex.solve()
solve_result_check(box_and_vertex._solve_result)
vertex_translation_part = (
box_and_vertex.children[0].loc.wrapped.Transformation().TranslationPart()
)
@ -356,6 +365,7 @@ def test_PointInPlane_param(box_and_vertex, param0, param1):
box_and_vertex.constrain("vertex", "box@faces@>Z", "PointInPlane", param=param0)
box_and_vertex.constrain("vertex", "box@faces@>X", "PointInPlane", param=param1)
box_and_vertex.solve()
solve_result_check(box_and_vertex._solve_result)
vertex_translation_part = (
box_and_vertex.children[0].loc.wrapped.Transformation().TranslationPart()
@ -364,9 +374,9 @@ def test_PointInPlane_param(box_and_vertex, param0, param1):
assert vertex_translation_part.X() - 0.5 == pytest.approx(param1, abs=1e-6)
def test_constraint_getPlane():
def test_constraint_getPln():
"""
Test that _getPlane does the right thing with different arguments
Test that _getPln does the right thing with different arguments
"""
ids = (0, 1)
sublocs = (cq.Location(), cq.Location())
@ -377,11 +387,19 @@ def test_constraint_getPlane():
def fail_this(shape0):
c0 = make_constraint(shape0)
with pytest.raises(ValueError):
c0._getPlane(c0.args[0])
c0._getPln(c0.args[0])
def resulting_pln(shape0):
c0 = make_constraint(shape0)
return c0._getPln(c0.args[0])
def resulting_plane(shape0):
c0 = make_constraint(shape0)
return c0._getPlane(c0.args[0])
p0 = resulting_pln(shape0)
return cq.Plane(
cq.Vector(p0.Location()),
cq.Vector(p0.XAxis().Direction()),
cq.Vector(p0.Axis().Direction()),
)
# point should fail
fail_this(cq.Vertex.makeVertex(0, 0, 0))
@ -423,7 +441,7 @@ def test_constraint_getPlane():
wonky_shape = cq.Wire.makePolygon(points3)
fail_this(wonky_shape)
# all faces should succeed
# all makePlane faces should succeed
for length, width in product([None, 10], [None, 11]):
f0 = cq.Face.makePlane(
length=length, width=width, basePnt=(1, 2, 3), dir=(1, 0, 0)
@ -482,3 +500,36 @@ def test_toCompound(simple_assy, nested_assy):
assert cq.Vector(0, 0, 18) in [x.Center() for x in c3.Faces()]
# also check with bounding box
assert c3.BoundingBox().zlen == pytest.approx(18)
@pytest.mark.parametrize("origin", [(0, 0, 0), (10, -10, 10)])
@pytest.mark.parametrize("normal", [(0, 0, 1), (-1, -1, 1)])
def test_infinite_face_constraint_PointInPlane(origin, normal):
"""
An OCCT infinite face has a center at (1e99, 1e99), but when a user uses it
in a constraint, the center should be basePnt.
"""
f0 = cq.Face.makePlane(length=None, width=None, basePnt=origin, dir=normal)
c0 = cq.assembly.Constraint(
("point", "plane"),
(cq.Vertex.makeVertex(10, 10, 10), f0),
sublocs=(cq.Location(), cq.Location()),
kind="PointInPlane",
)
p0 = c0._getPln(c0.args[1]) # a gp_Pln
derived_origin = cq.Vector(p0.Location())
assert derived_origin == cq.Vector(origin)
@pytest.mark.parametrize("kind", ["Plane", "PointInPlane", "Point"])
def test_infinite_face_constraint_Plane(kind):
assy = cq.Assembly(cq.Workplane().sphere(1), name="part0")
assy.add(cq.Workplane().sphere(1), name="part1")
assy.constrain(
"part0", cq.Face.makePlane(), "part1", cq.Face.makePlane(), kind,
)
assy.solve()
assert solve_result_check(assy._solve_result)