def test_construction(self): # Parameters t1 = ScaleAxes(1, 1, 1) t2 = ScaleAxes(2, 2, 2) t3 = ScaleAxes(3, 3, 3) # Valid self.assertEqual(Chained([t1, t2, t3]).transforms, [t1, t2, t3]) with self.assertNothingRaised(): chained0 = Chained([]) # No children with self.assertNothingRaised(): chained1 = Chained([t1, t2, t3]) # Regular with self.assertNothingRaised(): chained2 = Chained([t1, t1, t1]) # Repeated child with self.assertNothingRaised(): chained3 = Chained([chained1, t1, chained2]) # Chained child # From generator self.assertEqual(Chained(tf for tf in [t1, t2, t3]), Chained([t1, t2, t3])) # Invalid with self.assertRaises(TypeError): Chained(None) with self.assertRaises(TypeError): Chained([None]) with self.assertRaises(TypeError): Chained([1])
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_multiplication(self): # Create some transform r = RotateXyz(60, 30, 15) s = ScaleAxes (1, 2, -1) t = Translate([30, 20, 10]) # 2-chained self.assertEqual(r * s, Chained([r, s])) # 3-chained self.assertEqual( r * s * t , Chained([r, s, t])) self.assertEqual( (r * s) * t , Chained([r, s, t])) self.assertEqual( r * (s * t) , Chained([r, s, t])) # Multiplication is associative, but not commutative (kind-of already follows from the other tests) self.assertEqual ( (r * s) * t, r * (s * t) ) self.assertNotEqual( r * s , s * r ) # 4-chained self.assertEqual( r * s * r * s , Chained([r, s, r, s])) self.assertEqual( (r * s) * (r * s) , Chained([r, s, r, s])) self.assertEqual( ((r * s) * r) * s , Chained([r, s, r, s])) self.assertEqual( r * (s * (r * s)) , Chained([r, s, r, s])) rs = r * s self.assertEqual( rs * rs , Chained([r, s, r, s])) # Empty chained self.assertEqual(Chained([]) * Chained([]), Chained([])) self.assertEqual(Chained([]) * r, Chained([r])) self.assertEqual(r * Chained([]), Chained([r]))
def _equivalent(self): transform_axis = self._axis.closest_axis() forward_rotation = RotateFromTo(self._axis, transform_axis) scale = ScaleAxes(*(transform_axis * self._factor).replace(0, 1)) back_rotation = RotateFromTo(transform_axis, self._axis) return back_rotation * scale * forward_rotation
def test_equality(self): r = RotateXyz(60, 30, 15) s = ScaleAxes(1, 2, -1) t = Translate([60, 30, 15]) c = Chained([r, s, t]) # Same object self.assertEqualToItself(c) self.assertEqualToItself(Chained([])) # Equal objects self.assertEqual(Chained([]), Chained([])) self.assertEqual(Chained([r, s, t]), Chained([r, s, t])) # Identical children self.assertEqual( Chained([ RotateXyz(60, 30, 15), ScaleAxes(60, 30, 15), Translate([60, 30, 15]) ]), Chained([ RotateXyz(60, 30, 15), ScaleAxes(60, 30, 15), Translate([60, 30, 15]) ])) # Equal children # Different objects self.assertNotEqual(Chained([r, s, t]), Chained([r, s])) # Different number of children self.assertNotEqual(Chained([r, s, t]), Chained([r, t, s])) # Different order of children self.assertNotEqual( Chained([ RotateXyz(60, 30, 15), RotateXyz(60, 30, 15), Translate([60, 30, 15]) ]), Chained([ RotateXyz(60, 30, 15), RotateXyz(60, 30, 15), Translate([60, 30, 16]) ])) # Unequal children
def test_multiplication_with_object_and_transform(self): # Multiplication is used for both intersection (Object * Object) and transform (Transform * Object) a = Sphere(2) b = Cuboid(3, 3, 3) s = ScaleAxes(1, 2, -1) t = Translate([0, 0, 0]) self.assertEqual( t * (a * b), Transformed(t, Intersection([a, b]))) self.assertEqual((t * a) * b , Intersection([Transformed(t, a), b])) self.assertEqual((s * t) * a , Transformed(Chained([s, t]), a)) self.assertEqual( s * (t * a), Transformed(Chained([s, t]), a))
def test_postfix_transform(self): cube = Cuboid(11, 11, 11) # Vectors rv = [60, 34, 30] sv = [ 2, 1, 1] tv = [10, 20, 30] # Transforms r = RotateXyz(*rv) s = ScaleAxes (*sv) t = Translate(tv) # Long shortcuts self.assertEqual(cube.rotate (xyz = rv).scale(sv) .translate(tv), t * s * r * cube) self.assertEqual(cube.transform(r) .scale(sv) .transform(t) , t * s * r * cube) self.assertEqual(cube.rotate (xyz = rv).transform(s).translate(tv), t * s * r * cube) self.assertEqual(cube.transform(s * r) .transform(t) , t * s * r * cube) self.assertEqual(cube.scale([1, 2, 3]), ScaleAxes(1, 2, 3) * cube) self.assertEqual(cube.scale(2), ScaleUniform(2) * cube) self.assertEqual(cube.scale([1, 2, 3], 4), ScaleAxisFactor([1, 2, 3], 4) * cube) # Error with self.assertRaises(TypeError): cube.transform(cube)
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_to_scad(self): r = RotateXyz(60, 30, 15) s = ScaleAxes(1, 2, -1) cube = Cuboid(11, 11, 11) # Simple transform self.assertEqual( Transformed(r, cube).to_scad(), ScadObject("rotate", [[60, 30, 15]], None, [ ScadObject("cube", [[11, 11, 11]], None, None), ])) # Chained transform self.assertEqual( Transformed(Chained([r, s]), cube).to_scad(), ScadObject("rotate", [[60, 30, 15]], None, [ ScadObject("scale", [[1, 2, -1]], None, [ ScadObject("cube", [[11, 11, 11]], None, None), ]) ]))
def test_to_tree(self): a = Sphere(2) b = Cuboid(3, 3, 3) s = ScaleAxes(1, 2, -1) t = Translate([0, 0, 0]) part = a + s*t*b actual = part.to_tree() expected = Node(part, [ Node(a), Node(s*t*b, [ Node(s*t, [ Node(s), Node(t), ]), Node(b), ]), ]) self.assertEqual(actual, expected)
def test_to_scad(self): # Create some transforms r = RotateXyz(60, 30, 15) s = ScaleAxes(1, 2, -1) t = Translate([30, 20, 10]) # Non-empty chained with None target self.assertEqual( Chained([r, s, t]).to_scad(None), ScadObject("rotate", [[60, 30, 15]], None, [ ScadObject( "scale", [[1, 2, -1]], None, [ScadObject("translate", [[30, 20, 10]], None, None)]) ])) # Empty chained with valid target dummy = ScadObject("dummy", None, None, None) self.assertEqual(Chained([]).to_scad(dummy), dummy) # Empty chained with None target self.assertEqual( Chained([]).to_scad(None), ScadObject(None, None, None, None))
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 test_inverse(self): tf1 = ScaleAxes(1, 2, -1) * Translate([1, 2, 3]) tf2 = Translate([-1, -2, -3]) * ScaleAxes(1, 0.5, -1) self.assertInverse(tf1, tf2) self.assertInverse(Chained([]), Chained([]))
from cadlib.object.primitives import Cuboid, Cylinder, Sphere from cadlib.transform.primitives import RotateXyz, ScaleAxes, Translate from cadlib.util.vector import Z def test_case(name, object): print(name) print(" Cadlib tree:") print(object.to_tree().format(top_indent=" ")) print(" OpenSCAD tree:") print(object.to_scad().to_tree().format(top_indent=" ")) print(" OpenSCAD source:") print(object.to_scad().to_code(top_indent=" ")) o1 = Cuboid(10, 10, 10) o2 = Cylinder(Z, 5, 5) o3 = Sphere(2) rotate = RotateXyz(90, 0, 45) scale = ScaleAxes(1, 1, 2) translate = Translate([10, 10, 2]) test_case("Translated object", translate * o1) test_case("Intersection", o1 * o2 * o3) test_case("Complex", rotate * (translate * scale * (o2 + o3) + o1))