def direction_length(cls, direction, length, base_radius, cap_radius): direction = Vector.convert(direction, "direction", required_length=3) length = number.convert(length, "length") if direction.is_zero: raise ValueError("direction must be non-zero") base = Vector(0, 0, 0) cap = direction.normalized() * length return cls(base, cap, base_radius, cap_radius)
def __init__(self, base, cap, base_radius, cap_radius): super().__init__() self._base = Vector.convert(base, "base", required_length=3) self._cap = Vector.convert(cap, "cap", required_length=3) self._base_radius = number.convert(base_radius, "base_radius") self._cap_radius = number.convert(cap_radius, "cap_radius") # Warn if the cap is equal to the base (zero length) or both radii are # zero (zero thickness; note that it is allowed for *one* of the radii # to be zero). if self._base == self._cap: warn("length is 0") if self._base_radius == self._cap_radius == 0: warn("radius is 0")
def test_scale(self): # Canonical axis/factor self.assertEqual(scale(axis = Vector(1, 2, 3), factor = 4), ScaleAxisFactor(Vector(1, 2, 3), 4)) self.assertEqual(scale(axis = [1, 2, 3], factor = 4), ScaleAxisFactor( [1, 2, 3], 4)) # Canonical XYZ self.assertEqual(scale(xyz = Vector(1, 2, 3)), ScaleAxes(1, 2, 3)) self.assertEqual(scale(xyz = [1, 2, 3]), ScaleAxes(1, 2, 3)) # Canonical uniform self.assertEqual(scale(factor = 2), ScaleUniform(2)) # Convenience axis/factor (implicit) self.assertEqual(scale(Vector(1, 2, 3), 4), ScaleAxisFactor(Vector(1, 2, 3), 4)) self.assertEqual(scale( [1, 2, 3], 4), ScaleAxisFactor( [1, 2, 3], 4)) # Convenience axis/factor (explicit) self.assertEqual(scale(Vector(1, 2, 3), factor = 4), ScaleAxisFactor(Vector(1, 2, 3), 4)) self.assertEqual(scale( [1, 2, 3], factor = 4), ScaleAxisFactor( [1, 2, 3], 4)) # Convenience XYZ self.assertEqual(scale(Vector(1, 2, 3)), ScaleAxes(1, 2, 3)) self.assertEqual(scale( [1, 2, 3]), ScaleAxes(1, 2, 3)) # Convenience uniform self.assertEqual(scale(2), ScaleUniform(2))
def test_equality(self): # Same object self.assertEqualToItself(RotateFromTo(Vector(1, 2, 3), Vector(4, 5, 6))) # Equal objects self.assertEqual(RotateFromTo([1, 2, 3], [4, 5, 6]), RotateFromTo([1, 2, 3], [4, 5, 6])) # Equal # Different objects self.assertNotEqual(RotateFromTo([1, 2, 3], [4, 5, 6]), RotateFromTo([1, 2, 3], [4, 5, 7])) # Different from self.assertNotEqual(RotateFromTo([1, 2, 3], [4, 5, 6]), RotateFromTo([1, 2, 7], [4, 5, 6])) # Different to # Equal objects from different specifications self.assertEqual(RotateFromTo(Vector(1, 2, 3), [4, 5, 6]), RotateFromTo([1, 2, 3], [4, 5, 6])) self.assertEqual(RotateFromTo([1, 2, 3], Vector(4, 5, 6)), RotateFromTo([1, 2, 3], [4, 5, 6]))
def __init__(self, frm, to, ignore_ambiguity = False): frm = Vector.convert(frm, "frm", required_length=3) to = Vector.convert(to , "to" , required_length=3) if frm.is_zero: raise ValueError("frm may not be zero-length") if to.is_zero: raise ValueError("frm may not be zero-length") if not ignore_ambiguity: if frm.collinear(to) and frm.dot(to) < 0: warn("Rotation from {} to {} is ambiguous because the vectors are colinear and opposite" .format(frm, to), UserWarning, 2) self._frm = frm self._to = to
def cylinder(direction_or_base, length_or_cap, r=None, d=None): """Generate a cylinder. Signatures (convenience forms only): * cylinder(direction, length, radius) * cylinder(direction, length, d = diameter) * cylinder(base, cap, radius) * cylinder(base, cap, d = diameter) """ # Radius or diameter radius = _get_radius(r, d) # length_or_base must be a vector or a number if number.valid(length_or_cap): # Number - direction/length return Frustum.direction_length(direction_or_base, length_or_cap, radius, radius) elif Vector.valid_type(length_or_cap): # Vector type - base/cap return Frustum(direction_or_base, length_or_cap, radius, radius) else: raise TypeError( "Invalid call signature: length_or_cap must be a vector type or a number" )
def __init__(self, axis, angle=None): axis = Vector.convert(axis, "axis", required_length=3) if axis.is_zero: raise ValueError("axis may not be zero-length") self._axis = axis self._angle = number.convert(angle, "angle", default=self._axis.length)
def frustum(direction_or_base, length_or_cap, r=None, d=None): """Generate a frustum. For the forms with a direction, the base will be at the origin. Signatures (convenience forms only): * frustum (direction, length, radii) * frustum (direction, length, d = diameters) * frustum (base, cap, radii) * frustum (base, cap, d = diameters) """ # Radius or diameter radii = _get_radii(r, d) # length_or_base must be a vector or a number if number.valid(length_or_cap): # Number - direction/length return Frustum.direction_length(direction_or_base, length_or_cap, *radii) elif Vector.valid_type(length_or_cap): # Vector type - base/cap return Frustum(direction_or_base, length_or_cap, *radii) else: raise TypeError( "Invalid call signature: length_or_cap must be a vector type or a number" )
def cone(direction_or_base, length_or_tip, r=None, d=None): """Generate a cone. For the forms with a direction, the base will be at the origin. Signatures (convenience forms only): * cone(direction, length, radius) * cone(direction, length, d = diameter) * cone(base, cap, radius) * cone(base, cap, d = diameter) """ # TODO should use r = None, *, d = None? (not just here) # Radius or diameter radius = _get_radius(r, d) # length_or_base must be a vector or a number if number.valid(length_or_tip): # Number - direction/length return Frustum.direction_length(direction_or_base, length_or_tip, radius, 0) elif Vector.valid_type(length_or_tip): # Vector type - base/cap return Frustum(direction_or_base, length_or_tip, radius, 0) else: raise TypeError( "Invalid call signature: length_or_cap must be a vector type or a number" )
def __init__(self, axis, factor): axis = Vector.convert(axis, "axis", required_length=3) if axis.is_zero: raise ValueError("axis may not be zero-length") self._axis = axis self._factor = number.convert(factor, "factor", default=self._axis.length) if self._factor == 0: warn("factor is 0")
def test_construction(self): # Valid RotateFromTo( [1, 2, 3], [4, 5, 6]) RotateFromTo(Vector(1, 2, 3), Vector(4, 5, 6)) # Invalid with self.assertRaises(TypeError ): RotateFromTo([1, 2, 3], 45 ) with self.assertRaises(TypeError ): RotateFromTo(45, [1, 2, 3]) with self.assertRaises(TypeError ): RotateFromTo([1, 2, "3"], [4, 5, 6]) with self.assertRaises(TypeError ): RotateFromTo([1, 2, 3 ], [4, 5, "6"]) # Zero vectors o = Vector.zero(3) with self.assertRaises(ValueError): RotateFromTo(X, o) with self.assertRaises(ValueError): RotateFromTo(X, o) with self.assertRaises(ValueError): RotateFromTo(o, o) # Opposite direction (ambiguous rotation) with self.assertWarns(UserWarning): RotateFromTo([1, 0, 0], [-1, 0, 0]) with self.assertWarns(UserWarning): RotateFromTo([1, 2, 3], [-2, -4, -6])
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 test_equality(self): # Same object self.assertEqualToItself(ScaleAxisFactor([1, 2, 3], 4)) # Equal objects self.assertEqual(ScaleAxisFactor([1, 2, 3], 4), ScaleAxisFactor([1, 2, 3], 4)) # Different objects self.assertNotEqual(ScaleAxisFactor([1, 2, 3], 4), ScaleAxisFactor([1, 2, 4], 5)) self.assertNotEqual(ScaleAxisFactor([1, 2, 3], 4), ScaleAxisFactor([1, 2, 3], 5)) # Equal objects from different specifications self.assertEqual(ScaleAxisFactor(Vector(1, 2, 3), 4), ScaleAxisFactor([1, 2, 3], 4))
def test_construction(self): # Valid ScaleAxisFactor([1, 2, 3], 4) ScaleAxisFactor(Vector(1, 2, 3), 4) # Valid, but with warning with self.assertWarns(UserWarning): ScaleAxisFactor([1, 2, 3], 0) # Valid, though useless # Invalid with self.assertRaises(ValueError): ScaleAxisFactor([0, 0, 0], 4) with self.assertRaises(TypeError): ScaleAxisFactor([1, 2, "3"], 4) with self.assertRaises(TypeError): ScaleAxisFactor([1, 2, 3], "4") with self.assertRaises(TypeError): ScaleAxisFactor(1, "4")
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_to_scad(self): self.ignore_scad_comments = True # Special case: axis aligned with one of the coordinate axes # X self.assertEqual( ScaleAxisFactor(X, 2).to_scad(None), ScadObject("scale", [[2, 1, 1]], None, None)) # Y self.assertEqual( ScaleAxisFactor(Y, 3).to_scad(None), ScadObject("scale", [[1, 3, 1]], None, None)) # Z self.assertEqual( ScaleAxisFactor(Z, 4).to_scad(None), ScadObject("scale", [[1, 1, 4]], None, None)) # -Z self.assertEqual( ScaleAxisFactor(-Z, 4).to_scad(None), ScadObject("scale", [[1, 1, 4]], None, None)) # Special case: factor 1 (aligned or non-aligned) self.assertEqual( ScaleAxisFactor(X, 1).to_scad(None), ScadObject("scale", [[1, 1, 1]], None, None)) self.assertEqual( ScaleAxisFactor(Vector(1, 1, 0), 1).to_scad(None), ScadObject("scale", [[1, 1, 1]], None, None)) # General case, with the scale axis in the Y/Z plane. The scale axis is # rotated to the X axis. # Note that we're comparing floating point numbers here - rounding # errors might become an issue. angle = math.atan(1 / 2) / degree self.assertEqual( ScaleAxisFactor([2, 1, 0], 3).to_scad(None), ScadObject("rotate", None, [ ('a', angle), ('v', [0.0, 0.0, 1.0]) ], [ ScadObject("scale", [[3, 1, 1]], None, [ ScadObject("rotate", None, [('a', angle), ('v', [0.0, 0.0, -1.0])], None) ]) ]))
def test_cuboid_generators(self): self.assertEqual(cuboid(1), Cuboid(1, 1, 1)) self.assertEqual(cuboid(1, 2, 3), Cuboid(1, 2, 3)) self.assertEqual(cuboid([1, 2, 3]), Cuboid(1, 2, 3)) self.assertEqual(cuboid((1, 2, 3)), Cuboid(1, 2, 3)) self.assertEqual(cuboid(Vector(1, 2, 3)), Cuboid(1, 2, 3)) self.assertEqual(cube(1), Cuboid(1, 1, 1)) with self.assertRaises(TypeError): cuboid(1, 2) with self.assertRaises(TypeError): cuboid(None) with self.assertRaises(TypeError): cuboid("1") with self.assertRaises(TypeError): cuboid(1, 2, None) with self.assertRaises(TypeError): cuboid(1, None, 2) with self.assertRaises(TypeError): cuboid(1, 2, "3")
def cuboid(size_or_x_or_xyz, y=None, z=None): """Generate a cuboid. The cuboid will have one corner at the origin, will be aligned with the axes, and extend in the positive axis directions. Signatures (convenience forms only): * cuboid(size) * cuboid(x, y, z) * cuboid(xyz) xzy can be Vector, list, or tuple. Note that for convenience, a Vector can be used for xyz even though xyz is not strictly a vector. """ if both(y, z): return Cuboid(size_or_x_or_xyz, y, z) elif neither(y, z) and number.valid(size_or_x_or_xyz): return Cuboid(size_or_x_or_xyz, size_or_x_or_xyz, size_or_x_or_xyz) elif neither(y, z) and Vector.valid_type(size_or_x_or_xyz): return Cuboid(*size_or_x_or_xyz) else: raise TypeError("y and z can only be specified together")
def to_scad(self): length = (self._cap - self._base).length direction = (self._cap - self._base).normalized() # Create the cylinder if self._base_radius == self._cap_radius: cylinder = ScadObject("cylinder", [length], [('r', self._base_radius)], None) else: cylinder = ScadObject("cylinder", [length], [('r1', self._base_radius), ('r2', self._cap_radius)], None) # Rotate to the correct orientation (skip if it is along the Z axis) if direction != Z: cylinder = RotateFromTo(frm=Z, to=direction, ignore_ambiguity=True).to_scad(cylinder) # Move to the correct position (skip if the base is at the origin) if self._base != Vector(0, 0, 0): cylinder = Translate(self._base).to_scad(cylinder) return cylinder
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 scale(xyz_or_axis_or_factor = None, factor = None, xyz = None, axis = None): """Generate a scaling transform around the origin. Signatures (canonical forms): * scale(xyz = [2, 1, 1]) * scale(factor = 2) * scale(axis = X, factor = 2) Signatures (convenience forms): * scale([2, 1, 1]) * scale(2) * scale(X, 2) Vectors can be specified as Vector, list, or tuple. Note that a Vector can be used for xyz even though xyz is not strictly a vector. """ # Canonical forms: # xyz_or_axis_or_factor factor xyz axis # - - list - # XYZ # - num - vec # Axis/factor # - num - - # Uniform # Convenience forms (-: must be None, *: overwritten) # vec num - * # Axis/factor (implicit or explicit) # vec - * - # XYZ # num * - - # Isotropic XYZ # # Make sure that there are no conflicts between convenience parameters and canonical parameters if both(xyz_or_axis_or_factor, xyz ): raise TypeError("xyz" " cannot be specified together with xyz_or_axis") if both(xyz_or_axis_or_factor, axis): raise TypeError("axis" " cannot be specified together with xyz_or_axis") # Transform the convenience forms to canonical form if xyz_or_axis_or_factor is not None: if Vector.valid_type(xyz_or_axis_or_factor): if factor is None: # Xyz xyz = xyz_or_axis_or_factor else: # Axis part of axis/factor axis = xyz_or_axis_or_factor elif number.valid(xyz_or_axis_or_factor): if factor is None: # Factor factor = xyz_or_axis_or_factor else: raise TypeError("factor cannot be specified together with numeric xyz_or_axis") else: raise TypeError("xyz_or_axis_or_factor must be a vector type or a number") # Check the parameters that must appear in pairs if axis is not None and factor is None: raise TypeError("factor" " is required when " "axis" " is given") # Handle the different cases if axis is not None: # Check that no other specification is given if xyz is not None: raise TypeError("xyz" " cannot be specified together with axis") return ScaleAxisFactor(axis, factor) 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 xyz") if factor is not None: raise TypeError("factor" " cannot be specified together with xyz") return ScaleAxes(*xyz) elif factor is not None: return ScaleUniform(factor) else: raise TypeError("Invalid call signature")
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_frustum_generators(self): v0 = Vector(0, 0, 0) # Cylinder - base/cap self.assertEqual(cylinder(X, Y, 2), Frustum(X, Y, 2, 2)) self.assertEqual(cylinder(X, Y, r=2), Frustum(X, Y, 2, 2)) self.assertEqual(cylinder(X, Y, d=4), Frustum(X, Y, 2, 2)) self.assertEqual(cylinder(origin, Y, 2), Frustum(origin, Y, 2, 2)) # 0 is allowed as base # 0 is not allowed as cap because it would be interpreted as # direction/length # Cylinder - direction/length self.assertEqual(cylinder(X, 1, 2), Frustum(origin, X, 2, 2)) self.assertEqual(cylinder(X, 1, r=2), Frustum(origin, X, 2, 2)) self.assertEqual(cylinder(X, 1, d=4), Frustum(origin, X, 2, 2)) # Cylinder - invalid with self.assertNothingRaised(): cylinder(X, 5, 1) # Reference with self.assertRaises(TypeError): cylinder(5, 5, 1) # First is not a vector with self.assertRaises(TypeError): cylinder(X, "", 1) # Second is not a vector or number with self.assertRaises(TypeError): cylinder(X, 5) # Neither radius nor diameter with self.assertRaises(TypeError): cylinder(X, 5, 1, 1) # Both radius and diameter with self.assertRaises(ValueError): cylinder(v0, 5, 1) # Zero vector is not allowed as direction with self.assertRaises(TypeError): cylinder(X, 5, r=(1, 2)) # Radii with self.assertRaises(TypeError): cylinder(X, 5, d=(1, 2)) # Diameters # Cone - base/tip self.assertEqual(cone(X, Y, 2), Frustum(X, Y, 2, 0)) self.assertEqual(cone(X, Y, r=2), Frustum(X, Y, 2, 0)) self.assertEqual(cone(X, Y, d=4), Frustum(X, Y, 2, 0)) # Cone - direction/length self.assertEqual(cone(X, 1, 2), Frustum(origin, X, 2, 0)) self.assertEqual(cone(X, 1, r=2), Frustum(origin, X, 2, 0)) self.assertEqual(cone(X, 1, d=4), Frustum(origin, X, 2, 0)) # Cone - invalid with self.assertNothingRaised(): cone(X, 5, 1) # Reference with self.assertRaises(TypeError): cone(5, 5, 1) # First is not a vector with self.assertRaises(TypeError): cone(X, "", 1) # Second is not a vector or number with self.assertRaises(TypeError): cone(X, 5) # Neither radius nor diameter with self.assertRaises(TypeError): cone(X, 5, 1, 1) # Both radius and diameter with self.assertRaises(ValueError): cone(v0, 5, 1) # Zero vector is not allowed as direction with self.assertRaises(TypeError): cone(v0, 5, r=(1, 2)) # Radii with self.assertRaises(TypeError): cone(v0, 5, d=(1, 2)) # Diameters # Frustum - base/cap self.assertEqual(frustum(X, Y, (2, 3)), Frustum(X, Y, 2, 3)) self.assertEqual(frustum(X, Y, r=(2, 3)), Frustum(X, Y, 2, 3)) self.assertEqual(frustum(X, Y, d=(4, 6)), Frustum(X, Y, 2, 3)) # Frustum - direction/length self.assertEqual(frustum(X, 1, (2, 3)), Frustum(origin, X, 2, 3)) self.assertEqual(frustum(X, 1, r=(2, 3)), Frustum(origin, X, 2, 3)) self.assertEqual(frustum(X, 1, d=(4, 6)), Frustum(origin, X, 2, 3)) # Frustum - invalid with self.assertNothingRaised(): frustum(X, 5, (1, 2)) # Reference with self.assertRaises(TypeError): frustum(5, 5, (1, 2)) # First is not a vector with self.assertRaises(TypeError): frustum(X, "", (1, 2)) # Second is not a vector or number with self.assertRaises(TypeError): frustum(X, 5) # Neither radii nor diameters with self.assertRaises(TypeError): frustum(X, 5, (1, 2), (1, 2)) # Both radii and diameters with self.assertRaises(ValueError): frustum(v0, 5, (2, 3)) # Zero vector is not allowed as direction with self.assertRaises(TypeError): frustum(X, 1, r=2) # Single radius with self.assertRaises(TypeError): frustum(X, 1, d=4) # Single diameter # Errors caught by _get_radii with self.assertRaises(TypeError): frustum(X, 1, r=(2, 3), d=(4, 6)) # Both radius and diameter with self.assertRaises(TypeError): frustum(X, 1) # Neither radius nor diameter with self.assertRaises(ValueError): frustum(X, 1, r=(2, 3, 4)) # Too many radii with self.assertRaises(ValueError): frustum(X, 1, d=(4, 6, 8)) # Too many diameters with self.assertRaises(ValueError): frustum(X, 1, r=(2, )) # Too few radii with self.assertRaises(ValueError): frustum(X, 1, d=(4, )) # Too few diameters
def __init__(self, vector): self._vector = Vector.convert(vector, "vector", required_length=3)
def test_translate(self): self.assertEqual(translate(Vector(1, 2, 3)), Translate(Vector(1, 2, 3))) self.assertEqual(translate( [1, 2, 3]), Translate(Vector(1, 2, 3)))
def __init__(self, object, name, position): # TODO remove name? self._object = object self._name = name self._position = Vector.convert(position, "position", required_length=3)
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