def test_to_matrix(self): self.assertAlmostEqual( RotateAxisAngle(X, 90).to_matrix(), affine_matrix(X, Z, -Y)) self.assertAlmostEqual( RotateAxisAngle(X + Y, 180).to_matrix(), affine_matrix(Y, X, -Z)) self.assertAlmostEqual( RotateAxisAngle(X + Y + Z, 120).to_matrix(), affine_matrix(Y, Z, X))
def to_matrix(self): axis, angle = self._to_axis_angle() if axis is None: # No rotation return Matrix.identity(4) else: # Yes rotation return RotateAxisAngle(axis, angle).to_matrix()
def to_scad(self, target): axis, angle = self._to_axis_angle() if axis is None: # No rotation. Generate a zero XYZ transform instead of simply # returning the target. This improves code clarity and also ensures # that a valid ScadObject is returned even if target is None. return RotateXyz(0, 0, 0).to_scad(target).comment(str(self)) else: # Yes rotation return RotateAxisAngle(axis.normalized(), angle).to_scad(target).comment(str(self))
def test_rotate(self): # Canonical axis/angle self.assertEqual(rotate(axis = Vector(1, 2, 3), angle = 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # Vector self.assertEqual(rotate(axis = [1, 2, 3], angle = 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # List # Canonical from/to self.assertEqual(rotate(frm = Vector(1, 2, 3), to = Vector(4, 5, 6)), RotateFromTo(Vector(1, 2, 3), Vector(4, 5, 6))) # Vectors self.assertEqual(rotate(frm = [1, 2, 3], to = [4, 5, 6]), RotateFromTo(Vector(1, 2, 3), Vector(4, 5, 6))) # Lists # Canonical XYZ self.assertEqual(rotate(xyz = [45, 0, 30]), RotateXyz(45, 0, 30)) # Canonical yaw/pitch/roll self.assertEqual(rotate(ypr = [90, -20, 5]), RotateYpr(90, -20, 5)) # Convenience axis/angle (implicit) self.assertEqual(rotate(Vector(1, 2, 3), 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # Vector self.assertEqual(rotate( [1, 2, 3], 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # List # Convenience axis/angle (explicit) self.assertEqual(rotate(Vector(1, 2, 3), angle = 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # Vector self.assertEqual(rotate( [1, 2, 3], angle = 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # List # Convenience from/to (implicit) self.assertEqual(rotate(X , Y ), RotateFromTo(X, Y)) # Vectors self.assertEqual(rotate([1, 0, 0], [0, 1, 0]), RotateFromTo(X, Y)) # Lists # Convenience from/to (explicit) self.assertEqual(rotate(X , to = Y ), RotateFromTo(X, Y)) # Vectors self.assertEqual(rotate([1, 0, 0], to = [0, 1, 0]), RotateFromTo(X, Y)) # Lists
def test_equality(self): # Same object self.assertEqualToItself(RotateAxisAngle(Vector(1, 2, 3), 45)) # Equal objects self.assertEqual(RotateAxisAngle(Vector(1, 2, 3), 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # Equal # Different objects self.assertNotEqual(RotateAxisAngle(Vector(1, 2, 3), 45), RotateAxisAngle(Vector(1, 2, 4), 45)) # Different axis self.assertNotEqual(RotateAxisAngle(Vector(1, 2, 3), 45), RotateAxisAngle(Vector(1, 2, 3), 46)) # Different angle # Equal objects from different specifications self.assertEqual(RotateAxisAngle([1, 2, 3], 45), RotateAxisAngle(Vector(1, 2, 3), 45)) # list vs. Vector
def test_inequality(self): # Different-type transformations are not equal (even if the values are identical) transforms = [ RotateAxisAngle(X, 0), RotateFromTo(X, X), RotateXyz(0, 0, 0), RotateYpr(0, 0, 0), ScaleAxisFactor(X, 1), ScaleUniform(1), ScaleAxes (1, 1, 1), Translate([0, 0, 0]), ] for t1 in transforms: for t2 in transforms: if t1 is not t2: self.assertNotEqual(t1, t2)
def test_multiplication_with_vector(self): r = RotateAxisAngle(X, 90) s = ScaleAxes (1, 2, -1) t = Translate([1, 2, 3]) self.assertAlmostEqual(r * Vector( 0, 0, 0), Vector( 0, 0, 0)) self.assertAlmostEqual(r * Vector(10, 20, 30), Vector(10, -30, 20)) self.assertAlmostEqual(s * Vector( 0, 0, 0), Vector( 0, 0, 0)) self.assertAlmostEqual(s * Vector(10, 20, 30), Vector(10, 40, -30)) self.assertAlmostEqual(t * Vector( 0, 0, 0), Vector( 1, 2, 3)) self.assertAlmostEqual(t * Vector(10, 20, 30), Vector(11, 22, 33)) self.assertAlmostEqual((t * s) * Vector(10, 20, 30) , Vector(11, 42, -27)) self.assertAlmostEqual( t * (s * Vector(10, 20, 30)), Vector(11, 42, -27)) with self.assertRaises(ValueError): r * Vector(0, 0)
def test_construction(self): # Valid RotateAxisAngle([1, 2, 3], 45) RotateAxisAngle(Vector(1, 2, 3), 45) # Invalid with self.assertRaises(ValueError): RotateAxisAngle([0, 0, 0], 45) with self.assertRaises(TypeError): RotateAxisAngle([1, 2, "3"], 4) with self.assertRaises(TypeError): RotateAxisAngle([1, 2, 3], "4") with self.assertRaises(TypeError): RotateAxisAngle(1, "4")
def rotate(axis_or_frm = None, angle_or_to = None, axis = None, angle = None, frm = None, to = None, xyz = None, ypr = None, ignore_ambiguity = False): """Generate a rotation around an axis through the origin. Signatures (canonical forms): * rotate(axis = x, angle = 45) * rotate(frm = x, to = y) * rotate(xyz = [45, 0, 30]) * rotate(ypr = [45, -30, 10]) Signatures (convenience forms): * rotate(x, 45) * rotate(x, angle = 45) * rotate(x, y) * rotate(x, to = y) """ # Canonical forms: # axis_or_axmag_or_frm angle_or_to axis angle frm to xyz ypr # - - vec num - - - - # Axis/angle # - - - - vec vec - - # From/to # - - - - - - list - # XYZ # - - - - - - - list # Yaw/pitch/roll # Convenience forms (-: must be None, *: overwritten) # vec num * * - - - - # Axis/angle (implicit) # vec - * num - - - - # Axis/angle (explicit) # vec vec - - * * - - # From/to (implicit) # vec - - - * vec - - # From/to (explicit) # # "Vector type" is Vector, list, or tuple # Make sure that there are no conflicts between convenience parameters and canonical parameters if both(axis_or_frm, axis ): raise TypeError("axis" " cannot be specified together with axis_or_frm") if both(axis_or_frm, frm ): raise TypeError("frm" " cannot be specified together with axis_or_frm") if both(angle_or_to, angle): raise TypeError("angle" " cannot be specified together with angle_or_to") if both(angle_or_to, to ): raise TypeError("to" " cannot be specified together with angle_or_to") # Transform the convenience forms to canonical form if axis_or_frm is not None: if not Vector.valid_type(axis_or_frm): raise TypeError("axis must be a vector type") if angle_or_to is not None: if number.valid(angle_or_to): # Axis/angle (implicit) axis = axis_or_frm angle = angle_or_to elif Vector.valid_type(angle_or_to): # From/to (implicit) frm = axis_or_frm to = angle_or_to else: raise TypeError("angle_or_to must be a number or a vector type") elif angle is not None: # Axis/angle (explicit) axis = axis_or_frm elif to is not None: # From/to (explicit) frm = axis_or_frm # Check the parameters that must appear in pairs if axis is not None and angle is None: raise TypeError("angle" " is required when " "axis" " is given") if angle is not None and axis is None: raise TypeError("axis" " is required when " "angle" " is given") if frm is not None and to is None: raise TypeError("to" " is required when " "frm" " is given") if to is not None and frm is None: raise TypeError("frm" " is required when " "to" " is given") # Handle the different cases if axis is not None: # Check that no other specification is given if frm is not None: raise TypeError("frm" " cannot be specified together with axis") if xyz is not None: raise TypeError("xyz" " cannot be specified together with axis") if ypr is not None: raise TypeError("ypr" " cannot be specified together with axis") return RotateAxisAngle(axis, angle) elif frm is not None: # Check that no other specification is given if axis is not None: raise TypeError("axis" " cannot be specified together with frm") if xyz is not None: raise TypeError("xyz" " cannot be specified together with frm") if ypr is not None: raise TypeError("ypr" " cannot be specified together with frm") return RotateFromTo(frm, to, ignore_ambiguity) elif xyz is not None: # Check that no other specification is given if axis is not None: raise TypeError("axis" " cannot be specified together with frm") if frm is not None: raise TypeError("frm" " cannot be specified together with axis") if ypr is not None: raise TypeError("ypr" " cannot be specified together with axis") return RotateXyz(*xyz) elif ypr is not None: # Check that no other specification is given if axis is not None: raise TypeError("axis" " cannot be specified together with frm") if frm is not None: raise TypeError("frm" " cannot be specified together with axis") if xyz is not None: raise TypeError("xyz" " cannot be specified together with axis") return RotateYpr(*ypr) else: raise TypeError("Invalid call signature")
def test_str(self): self.assertStr(RotateAxisAngle([1, 0, 0], 45), "Rotate by 45° around <1, 0, 0>")
def test_repr(self): self.assertRepr(RotateAxisAngle([1, 0, 0], 45), "RotateAxisAngle(Vector(1, 0, 0), 45)")
def test_to_scad(self): r = RotateAxisAngle([1, 2, 3], 45) self.assertScadObjectTarget(r, None, "rotate", None, [('a', 45), ('v', [1, 2, 3])], None)
def test_inverse(self): self.assertInverse(RotateAxisAngle(X, 45), RotateAxisAngle(-X, 45))