def test_construction(self): # Valid RotateYpr(10, 20, 30) # Invalid with self.assertRaises(TypeError): RotateYpr(1, 2, "3")
def test_equality(self): # Same object self.assertEqualToItself(RotateYpr(10, 20, 30)) # Equal objects self.assertEqual(RotateYpr(10, 20, 30), RotateYpr(10, 20, 30)) # Equal # Different objects self.assertNotEqual(RotateYpr(10, 20, 30), RotateYpr(10, 20, 40)) # Different values
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_str(self): self.assertStr(RotateYpr(0, 0, 0), "Yaw, pitch, and roll by 0°") self.assertStr(RotateYpr(1, 0, 0), "Yaw 1°") self.assertStr(RotateYpr(0, 2, 0), "Pitch 2°") self.assertStr(RotateYpr(0, 0, 3), "Roll 3°") self.assertStr(RotateYpr(1, 2, 0), "Yaw 1°, pitch 2°") self.assertStr(RotateYpr(1, 0, 3), "Yaw 1°, roll 3°") self.assertStr(RotateYpr(0, 2, 3), "Pitch 2°, roll 3°") self.assertStr(RotateYpr(1, 2, 3), "Yaw 1°, pitch 2°, roll 3°")
def test_rotate_shortcuts(self): self.assertEqual(shortcuts.yaw_left(1), RotateYpr(1, 0, 0)) self.assertEqual(shortcuts.yaw_right(2), RotateYpr(-2, 0, 0)) self.assertEqual(shortcuts.pitch_up(3), RotateYpr(0, 3, 0)) self.assertEqual(shortcuts.pitch_down(4), RotateYpr(0, -4, 0)) self.assertEqual(shortcuts.roll_right(5), RotateYpr(0, 0, 5)) self.assertEqual(shortcuts.roll_left(6), RotateYpr(0, 0, -6))
def test_to_scad(self): self.ignore_scad_comments = True # Since OpenSCAD does not have YPR rotations, they have to translated to # corresponding XYZ rotations. # A zero YPR transform is a zero XYZ transform (not an empty ScadObject, # which would also be possible). self.assertEqual( RotateYpr(0, 0, 0).to_scad(None), ScadObject("rotate", [[0, 0, 0]], None, None)) # A single-axis YPR rotation can be expressed as a single-axis YPR # rotation. # Yaw - Z axis self.assertEqual( RotateYpr(1, 0, 0).to_scad(None), ScadObject("rotate", [[0, 0, 1]], None, None)) # Pitch - X axis self.assertEqual( RotateYpr(0, 2, 0).to_scad(None), ScadObject("rotate", [[2, 0, 0]], None, None)) # Roll - Y axis self.assertEqual( RotateYpr(0, 0, 3).to_scad(None), ScadObject("rotate", [[0, 3, 0]], None, None)) # A dual-axis YPR rotation must be expressed as a chain of two single- # axis XYZ rotations (except in the case of yaw and pitch, which can be # combined). self.assertEqual( RotateYpr(0, 2, 3).to_scad(None), ScadObject("rotate", [[2, 0, 0]], None, [ScadObject("rotate", [[0, 3, 0]], None, None)])) self.assertEqual( RotateYpr(1, 0, 3).to_scad(None), ScadObject("rotate", [[0, 0, 1]], None, [ScadObject("rotate", [[0, 3, 0]], None, None)])) self.assertEqual( RotateYpr(1, 2, 0).to_scad(None), ScadObject("rotate", [[2, 0, 1]], None, None)) # A tripel-axis YPR rotation must be expressed as a chain of two # single-axis XYZ rotations (yaw and pitch can be combined). self.assertEqual( RotateYpr(1, 2, 3).to_scad(None), ScadObject("rotate", [[2, 0, 1]], None, [ScadObject("rotate", [[0, 3, 0]], None, None)]))
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_transform_shortcuts(self): a = Cuboid(11, 11, 11) self.assertEqual(a.up (1), Transformed(Translate([ 0, 0, 1]), a)) self.assertEqual(a.down (2), Transformed(Translate([ 0, 0, -2]), a)) self.assertEqual(a.left (3), Transformed(Translate([-3, 0, 0]), a)) self.assertEqual(a.right (4), Transformed(Translate([ 4, 0, 0]), a)) self.assertEqual(a.forward(5), Transformed(Translate([ 0, 5, 0]), a)) self.assertEqual(a.back (6), Transformed(Translate([ 0, -6, 0]), a)) self.assertEqual(a.yaw_left (1), Transformed(RotateYpr( 1, 0, 0), a)) self.assertEqual(a.yaw_right (2), Transformed(RotateYpr(-2, 0, 0), a)) self.assertEqual(a.pitch_up (3), Transformed(RotateYpr( 0, 3, 0), a)) self.assertEqual(a.pitch_down(4), Transformed(RotateYpr( 0, -4, 0), a)) self.assertEqual(a.roll_right(5), Transformed(RotateYpr( 0, 0, 5), a)) self.assertEqual(a.roll_left (6), Transformed(RotateYpr( 0, 0, -6), a))
def roll_left(r): return RotateYpr(0, 0, -r)
def roll_right(r): return RotateYpr(0, 0, r)
def pitch_down(p): return RotateYpr(0, -p, 0)
def pitch_up(p): return RotateYpr(0, p, 0)
def yaw_right(y): return RotateYpr(-y, 0, 0)
def yaw_left(y): return RotateYpr(y, 0, 0)
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_inverse(self): self.assertInverse(RotateYpr(10, 20, 30), RotateYpr(0, 0, -30) * RotateYpr(0, -20, 0) * RotateYpr(-10, 0, 0), symmetric=False)
def test_to_matrix(self): # No rotation self.assertAlmostEqual( RotateYpr(0, 0, 0).to_matrix(), affine_matrix(X, Y, Z)) # 90 degrees around a single axis self.assertAlmostEqual( RotateYpr(90, 0, 0).to_matrix(), affine_matrix(Y, -X, Z)) # Yaw left self.assertAlmostEqual( RotateYpr(0, 90, 0).to_matrix(), affine_matrix(X, Z, -Y)) # Pitch up self.assertAlmostEqual( RotateYpr(0, 0, 90).to_matrix(), affine_matrix(-Z, Y, X)) # Roll right # 180 degrees around a single axis self.assertAlmostEqual( RotateYpr(180, 0, 0).to_matrix(), affine_matrix(-X, -Y, Z)) # Yaw self.assertAlmostEqual( RotateYpr(0, 180, 0).to_matrix(), affine_matrix(X, -Y, -Z)) # Pitch self.assertAlmostEqual( RotateYpr(0, 0, 180).to_matrix(), affine_matrix(-X, Y, -Z)) # Roll # 90 degrees each around two axes self.assertAlmostEqual( RotateYpr(90, 90, 0).to_matrix(), affine_matrix(Y, Z, X)) # Yaw left, pitch up self.assertAlmostEqual( RotateYpr(90, 0, 90).to_matrix(), affine_matrix(-Z, -X, Y)) # Yaw left, roll right self.assertAlmostEqual( RotateYpr(0, 90, 90).to_matrix(), affine_matrix(Y, Z, X)) # Pitch up, roll right # 90 degrees each around all three axes self.assertAlmostEqual( RotateYpr(90, 90, 90).to_matrix(), affine_matrix(-X, Z, Y)) # Yaw left, pitch up, roll right
def test_repr(self): self.assertRepr(RotateYpr(1, 2, 3), "RotateYpr(1, 2, 3)")