From 79f220c050f0b2ebb3e52d9cf0c618364072b472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20S=C3=A1nchez=20de=20Le=C3=B3n=20Peque?= Date: Sun, 15 Dec 2019 20:15:01 +0100 Subject: [PATCH] Fix plane rotation method (again...) --- cadquery/occ_impl/geom.py | 24 +++++++---- tests/test_cadquery.py | 85 ++++++++++++++++++++++++++++++--------- 2 files changed, 84 insertions(+), 25 deletions(-) diff --git a/cadquery/occ_impl/geom.py b/cadquery/occ_impl/geom.py index 812f6dc2a..ff30c8267 100644 --- a/cadquery/occ_impl/geom.py +++ b/cadquery/occ_impl/geom.py @@ -634,19 +634,29 @@ def rotated(self, rotate=(0, 0, 0)): :param rotate: Vector [xDegrees, yDegrees, zDegrees] :return: a copy of this plane rotated as requested. """ - rotate = Vector(self.toWorldCoords(rotate)) + # NB: this is not a geometric Vector + rotate = Vector(rotate) # Convert to radians. rotate = rotate.multiply(math.pi / 180.0) # Compute rotation matrix. - m = Matrix() - m.rotateX(rotate.x) - m.rotateY(rotate.y) - m.rotateZ(rotate.z) + T1 = gp_Trsf() + T1.SetRotation( + gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.xDir.toTuple())), rotate.x + ) + T2 = gp_Trsf() + T2.SetRotation( + gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.yDir.toTuple())), rotate.y + ) + T3 = gp_Trsf() + T3.SetRotation( + gp_Ax1(gp_Pnt(*(0, 0, 0)), gp_Dir(*self.zDir.toTuple())), rotate.z + ) + T = Matrix(gp_GTrsf(T1 * T2 * T3)) # Compute the new plane. - newXdir = self.xDir.transform(m) - newZdir = self.zDir.transform(m) + newXdir = self.xDir.transform(T) + newZdir = self.zDir.transform(T) return Plane(self.origin, newXdir, newZdir) diff --git a/tests/test_cadquery.py b/tests/test_cadquery.py index 2057e58fa..88a17e84e 100644 --- a/tests/test_cadquery.py +++ b/tests/test_cadquery.py @@ -8,6 +8,8 @@ from random import random from random import randrange +from pytest import approx + # my modules from cadquery import * from cadquery import exporters @@ -283,27 +285,74 @@ def testPlaneRotateZNormal(self): """ Rotation of a plane in the Z direction should never alter its normal. - This test creates random planes, with the normal in a random direction - among positive and negative X, Y and Z. The plane is defined with this - normal and another random perpendicular vector (the X-direction of the - plane). The plane is finally rotated a random angle in the Z-direction - to verify that the resulting plane maintains the same normal. + This test creates random planes. The plane is rotated a random angle in + the Z-direction to verify that the resulting plane maintains the same + normal. + + The test also checks that the random origin is unaltered after + rotation. """ for _ in range(100): - normal_sign = choice((-1, 1)) - normal_dir = randrange(3) angle = (random() - 0.5) * 720 - - normal = [0, 0, 0] - normal[normal_dir] = normal_sign - xdir = [random(), random(), random()] - xdir[normal_dir] = 0 - - plane = Plane(origin=(0, 0, 0), xDir=xdir, normal=normal) - rotated = plane.rotated((0, 0, angle)).zDir.toTuple() - self.assertAlmostEqual(rotated[0], normal[0]) - self.assertAlmostEqual(rotated[1], normal[1]) - self.assertAlmostEqual(rotated[2], normal[2]) + xdir = Vector(random(), random(), random()).normalized() + rdir = Vector(random(), random(), random()).normalized() + zdir = xdir.cross(rdir).normalized() + origin = (random(), random(), random()) + plane = Plane(origin=origin, xDir=xdir, normal=zdir) + rotated = plane.rotated((0, 0, angle)) + assert rotated.zDir.toTuple() == approx(zdir.toTuple()) + assert rotated.origin.toTuple() == approx(origin) + + def testPlaneRotateConcat(self): + """ + Test the result of a well-known concatenated rotation example. + """ + xdir = (1, 0, 0) + normal = (0, 0, 1) + k = 2.0 ** 0.5 / 2.0 + origin = (2, -1, 1) + plane = Plane(origin=origin, xDir=xdir, normal=normal) + plane = plane.rotated((0, 0, 45)) + assert plane.xDir.toTuple() == approx((k, k, 0)) + assert plane.yDir.toTuple() == approx((-k, k, 0)) + assert plane.zDir.toTuple() == approx((0, 0, 1)) + plane = plane.rotated((0, 45, 0)) + assert plane.xDir.toTuple() == approx((0.5, 0.5, -k)) + assert plane.yDir.toTuple() == approx((-k, k, 0)) + assert plane.zDir.toTuple() == approx((0.5, 0.5, k)) + assert plane.origin.toTuple() == origin + + def testPlaneRotateConcatRandom(self): + """ + Rotation of a plane in a given direction should never alter that + direction. + + This test creates a plane and rotates it a random angle in a given + direction. After the rotation, the direction of the resulting plane + in the rotation-direction should be constant. + + The test also checks that the origin is unaltered after all rotations. + """ + origin = (2, -1, 1) + plane = Plane(origin=origin, xDir=(1, 0, 0), normal=(0, 0, 1)) + for _ in range(100): + before = { + 0: plane.xDir.toTuple(), + 1: plane.yDir.toTuple(), + 2: plane.zDir.toTuple(), + } + angle = (random() - 0.5) * 720 + direction = randrange(3) + rotation = [0, 0, 0] + rotation[direction] = angle + plane = plane.rotated(rotation) + after = { + 0: plane.xDir.toTuple(), + 1: plane.yDir.toTuple(), + 2: plane.zDir.toTuple(), + } + assert before[direction] == approx(after[direction]) + assert plane.origin.toTuple() == origin def testLoft(self): """