Exemple #1
0
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"
        )
Exemple #2
0
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"
        )
Exemple #3
0
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"
        )
Exemple #4
0
    def test_construction(self):
        with self.assertNothingRaised():
            Frustum(X, Y, 1, 0)

        # Zero size
        with self.assertWarnsRegex(UserWarning, r'length is 0'):
            Frustum(X, X, 1, 2)
        with self.assertWarnsRegex(UserWarning, r'length is 0'):
            Frustum(X, (1, 0, 0), 1, 2)

        # Zero is not allowed as a shortcut
        with self.assertRaises(TypeError):
            Frustum(0, Y, 1, 0)
        with self.assertRaises(TypeError):
            Frustum(X, 0, 1, 0)

        # Invalid
        Frustum(X, Y, 1, 2)  # Reference
        with self.assertRaises(TypeError):
            Frustum(1, Y, 1, 2)  # Base is not a vector
        with self.assertRaises(TypeError):
            Frustum(X, 1, 1, 2)  # Cap is not a vector
        with self.assertRaises(TypeError):
            Frustum(X, Y, X, 2)  # Base radius is not a number
        with self.assertRaises(TypeError):
            Frustum(X, Y, 1, X)  # Base radius is not a number
Exemple #5
0
    def test_to_scad(self):
        sphere = Sphere(2)
        cube = Cuboid(10, 10, 10)
        cylinder = Frustum(origin, Z, 11, 11)

        self.assertEqual(
            Difference([sphere, cube, cylinder]).to_scad(),
            ScadObject("difference", None, None, [
                sphere.to_scad(),
                cube.to_scad(),
                cylinder.to_scad(),
            ]))
Exemple #6
0
    def test_sum(self):
        sphere = Sphere(2)
        cube = Cuboid(10, 10, 10)
        cylinder = Frustum(origin, Z, 11, 11)
        objects = [sphere, cube, cylinder]

        self.assertEqual(sum(objects, Union.empty()), Union(objects))
Exemple #7
0
    def test_to_scad(self):
        sphere = Sphere(2)
        cube = Cuboid(10, 10, 10)
        cylinder = Frustum(origin, Z, 11, 11)

        self.assertEqual(
            Union([sphere, cube, cylinder]).to_scad(),
            ScadObject("union", None, None, [
                sphere.to_scad(),
                cube.to_scad(),
                cylinder.to_scad(),
            ]))

        # Empty
        self.assertEqual(
            Union([]).to_scad(), ScadObject("union", None, None, None))
Exemple #8
0
    def test_construction(self):
        sphere = Sphere(11)
        cube = Cuboid(22, 22, 22)
        cylinder = Frustum(origin, Z, 11, 11)
        object_list = [sphere, cube, cylinder]

        # Empty
        Csg([])

        # With objects
        self.assertEqual(Csg(object_list).children, object_list)

        # With generator
        self.assertEqual(Csg(o for o in object_list).children, object_list)

        # With invalid objects
        with self.assertRaises(TypeError):
            Csg(None)  # None instead of empty list
        with self.assertRaises(TypeError):
            Csg([Sphere])  # Class instead of object
        with self.assertRaises(TypeError):
            Csg(sphere)  # Object instead of list
        with self.assertRaises(TypeError):
            Csg([None])  # List with invalid value
        with self.assertRaises(TypeError):
            Csg([sphere, None])  # List with object and invalid value
Exemple #9
0
    def test_to_scad(self):
        sphere = Sphere(2)
        cube = Cuboid(10, 10, 10)
        cylinder = Frustum(origin, Z, 5, 5)

        mixed = Union(
            [Intersection([sphere, cylinder]),
             Difference([cube, sphere])])
        self.assertEqual(
            mixed.to_scad(),
            ScadObject("union", None, None, [
                ScadObject(
                    "intersection", None, None,
                    [sphere.to_scad(), cylinder.to_scad()]),
                ScadObject("difference", None, None,
                           [cube.to_scad(), sphere.to_scad()]),
            ]))
Exemple #10
0
    def test_csg_generators(self):
        sphere = Sphere(11)
        cuboid = Cuboid(11, 22, 33)
        cylinder = Frustum(origin, Z, 11, 11)
        object_list = [sphere, cuboid, cylinder]

        self.assertEqual(union(object_list), Union(object_list))
        self.assertEqual(difference(object_list), Difference(object_list))
        self.assertEqual(intersection(object_list), Intersection(object_list))
Exemple #11
0
    def test_equality(self):
        sphere = Sphere(11)
        cube = Cuboid(22, 22, 22)
        cylinder = Frustum(origin, Z, 11, 11)
        objects = [sphere, cube, cylinder]

        # Different types of CSG are not equal, even if their children are identical
        self.assertNotEqual(Union(objects), Intersection(objects))
        self.assertNotEqual(Intersection(objects), Difference(objects))
        self.assertNotEqual(Difference(objects), Union(objects))
Exemple #12
0
    def test_direction_length(self):
        self.assertEqual(Frustum.direction_length(X, 5, 1, 2),
                         Frustum(origin, X * 5, 1, 2))
        self.assertEqual(Frustum.direction_length((1, 0, 0), 5, 1, 2),
                         Frustum(origin, X * 5, 1, 2))

        # Zero direction
        with self.assertRaises(ValueError):
            Frustum.direction_length(origin, 5, 1, 2)

        # Zero size
        with self.assertWarnsRegex(UserWarning, r'length is 0'):
            Frustum.direction_length(X, 0, 1, 2)
Exemple #13
0
    def test_multiplication(self):
        a = Sphere(2)
        b = Cuboid(10, 10, 10)
        c = Frustum(origin, Z, 11, 11)
        d = Cuboid(20, 20, 20)

        self.assertEqual(    a  *  b             , Intersection([a, b      ]))
        self.assertEqual(    c  *  c             , Intersection([c, c      ]))
        self.assertEqual(   (a  *  b) * c        , Intersection([a, b, c   ]))
        self.assertEqual(    a  * (b  * c)       , Intersection([a, b, c   ]))
        self.assertEqual(    a  *  b  *  c  * d  , Intersection([a, b, c, d]))
        self.assertEqual(  ((a  *  b) *  c) * d  , Intersection([a, b, c, d]))
        self.assertEqual(    a  * (b  * (c  * d)), Intersection([a, b, c, d]))
        self.assertEqual(   (a  *  b) * (c  * d) , Intersection([a, b, c, d]))
Exemple #14
0
    def test_subtraction(self):
        a = Sphere(2)
        b = Cuboid(10, 10, 10)
        c = Frustum(origin, Z, 11, 11)
        d = Cuboid(20, 20, 20)

        # Difference is non-associative, so we get nested differences
        self.assertEqual(    a  -  b             , Difference([a, b      ]))
        self.assertEqual(    c  -  c             , Difference([c, c      ]))
        self.assertEqual(   (a  -  b) - c        , Difference([a, b, c   ]))
        self.assertEqual(    a  - (b  - c)       , Difference([a, Difference([b, c])]))
        self.assertEqual(    a  -  b  -  c  - d  , Difference([a, b, c, d]))
        self.assertEqual(  ((a  -  b) -  c) - d  , Difference([a, b, c, d]))
        self.assertEqual(    a  - (b  - (c  - d)), Difference([a, Difference([b, Difference([c, d])])]))
        self.assertEqual(   (a  -  b) - (c  - d) , Difference([Difference([a, b]), Difference([c, d])]))
Exemple #15
0
    def test_addition(self):
        a = Sphere(2)
        b = Cuboid(10, 10, 10)
        c = Frustum(origin, Z, 11, 11)
        d = Cuboid(20, 20, 20)

        self.assertEqual(   a +  b               , Union([a, b      ]))
        self.assertEqual(   c +  c               , Union([c, c      ]))
        self.assertEqual(  (a +  b) + c          , Union([a, b, c   ]))
        self.assertEqual(   a + (b  + c)         , Union([a, b, c   ]))
        self.assertEqual(    a  +  b  +  c  + d  , Union([a, b, c, d]))
        self.assertEqual(  ((a  +  b) +  c) + d  , Union([a, b, c, d]))
        self.assertEqual(    a  + (b  + (c  + d)), Union([a, b, c, d]))
        self.assertEqual(   (a  +  b) + (c  + d) , Union([a, b, c, d]))

        # Empty union
        self.assertEqual(Union([]) + Union([]), Union([]))
        self.assertEqual(Union([]) + a, Union([a]))
        self.assertEqual(a + Union([]), Union([a]))
Exemple #16
0
    def test_equality(self):
        sphere = Sphere(11)
        cuboid = Cuboid(11, 22, 33)
        cylinder = Frustum(origin, Z, 11, 11)
        objects = [sphere, cuboid, cylinder]

        # Same object
        self.assertEqualToItself(Union([]))
        self.assertEqualToItself(Union(objects))

        # Equal objects
        self.assertEqual(Union([]), Union([]))
        self.assertEqual(Union(objects), Union(objects))

        # Different objects
        self.assertNotEqual(Union(objects), Union([sphere, cuboid]))
        self.assertNotEqual(Union([cuboid, sphere]), Union([sphere, cuboid]))

        # Equal objects from different specifications
        self.assertEqual(Union.empty(), Union([]))
Exemple #17
0
    def test_render_to_file(self):
        part = Sphere(0.6) + Cuboid(1, 2, 3) - Frustum([0, 0, 0], [0, 0, 4], 0.5, 0.5)

        expected_file_name = os.path.join(os.path.dirname(__file__), "test_scad_object_render_to_file_expected.scad")
        actual_file_name   = os.path.join(os.path.dirname(__file__), "test_scad_object_render_to_file_actual.scad")

        self.assertTrue(os.path.isfile(expected_file_name))
        self.assertFalse(os.path.exists(actual_file_name))

        try:
            render_to_file(part, actual_file_name, fn=60)
            with open(actual_file_name) as actual_file:
                actual = actual_file.read()
            with open(expected_file_name) as expected_file:
                expected = expected_file.read()

            self.assertNotEqual(expected, "")
            self.assertEqual(actual, expected)
        finally:
            if os.path.exists(actual_file_name):
                os.unlink(actual_file_name)
Exemple #18
0
    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
Exemple #19
0
 def test_repr(self):
     self.assertRepr(Frustum(origin, 5 * X, 1, 2),
                     "Frustum(Vector(0, 0, 0), Vector(5, 0, 0), 1, 2)")
Exemple #20
0
    def test_equality(self):
        # Same object
        self.assertEqualToItself(Frustum(origin, X, 2, 1))

        # Equal objects
        self.assertEqual(Frustum(X, Y, 1, 2), Frustum(X, Y, 1, 2))
        self.assertEqual(Frustum(X, origin, 1, 2), Frustum(X, origin, 1, 2))

        # Different objects
        self.assertNotEqual(Frustum(X, Y, 1, 2), Frustum(Y, X, 1, 2))
        self.assertNotEqual(Frustum(X, Y, 1, 2), Frustum(X, Y, 2, 1))
        self.assertNotEqual(Frustum(X, Y, 1, 2), Frustum(Y, X, 2, 1))

        # Equal objects from different specifications
        self.assertEqual(Frustum(X, Y, 1, 2), Frustum((1, 0, 0), Y, 1, 2))
Exemple #21
0
 def test_str(self):
     self.assertStr(
         Frustum(Y, 5 * X, 1, 2),
         "Frustum with base <0, 1, 0> (base radius 1) and cap <5, 0, 0> (cap radius 2)"
     )
Exemple #22
0
    def test_to_scad(self):
        self.ignore_scad_comments = True

        # Cylinder along Z axis
        self.assertScadObject(Frustum(origin, 5 * Z, 1, 1), "cylinder", [5.0],
                              [('r', 1)])

        # Cone along Z axis
        self.assertScadObject(Frustum(origin, 5 * Z, 1, 0), "cylinder", [5.0],
                              [('r1', 1), ('r2', 0)])

        # Frustum along Z axis
        self.assertScadObject(Frustum(origin, 5 * Z, 2, 1), "cylinder", [5.0],
                              [('r1', 2), ('r2', 1)])

        # Cylinder along other axes (X, Y, -X, -Y, -Z)
        cylinder_scad = ScadObject("cylinder", [5], [('r', 1)], None)
        self.assertEqual(
            Frustum(origin, 5 * X, 1, 1).to_scad(),
            ScadObject("rotate", [], [('a', 90.0), ('v', [0.0, 1.0, 0.0])],
                       [cylinder_scad]))
        self.assertEqual(
            Frustum(origin, 5 * Y, 1, 1).to_scad(),
            ScadObject("rotate", [], [('a', 90.0), ('v', [-1.0, 0.0, 0.0])],
                       [cylinder_scad]))
        self.assertEqual(
            Frustum(origin, -5 * X, 1, 1).to_scad(),
            ScadObject("rotate", [], [('a', 90.0), ('v', [0.0, -1.0, 0.0])],
                       [cylinder_scad]))
        self.assertEqual(
            Frustum(origin, -5 * Y, 1, 1).to_scad(),
            ScadObject("rotate", [], [('a', 90.0), ('v', [1.0, 0.0, 0.0])],
                       [cylinder_scad]))
        self.assertEqual(
            Frustum(origin, -5 * Z, 1, 1).to_scad(),
            ScadObject("rotate", [], [('a', 180.0), ('v', [1.0, 0.0, 0.0])],
                       [cylinder_scad]))

        # Parallel to Z axis (shifted along X, X/Y, X/Y/Z)
        cylinder_scad = ScadObject("cylinder", [5], [('r', 1)], None)
        self.assertEqual(
            Frustum([1, 0, 0], [1, 0, 5], 1, 1).to_scad(),
            ScadObject("translate", [[1, 0, 0]], [], [cylinder_scad]))
        self.assertEqual(
            Frustum([1, 2, 0], [1, 2, 5], 1, 1).to_scad(),
            ScadObject("translate", [[1, 2, 0]], [], [cylinder_scad]))
        self.assertEqual(
            Frustum([1, 2, 3], [1, 2, 8], 1, 1).to_scad(),
            ScadObject("translate", [[1, 2, 3]], [], [cylinder_scad]))

        # Parallel to other axis (X)
        cylinder_scad = ScadObject("cylinder", [5], [('r', 1)], None)
        self.assertEqual(
            Frustum([0, 1, 2], [5, 1, 2], 1, 1).to_scad(),
            ScadObject("translate", [[0, 1, 2]], [], [
                ScadObject("rotate", [], [('a', 90.0), ('v', [0.0, 1.0, 0.0])],
                           [cylinder_scad])
            ]))

        # Not parallel to axis (in the XZ plane without and with translation)
        cylinder_scad = ScadObject("cylinder", [5 * math.sqrt(2)], [('r', 1)],
                                   None)
        self.assertEqual(
            Frustum([0, 0, 0], [5, 0, 5], 1, 1).to_scad(),
            ScadObject("rotate", [], [('a', 45.0), ('v', [0.0, 1.0, 0.0])],
                       [cylinder_scad]))
        self.assertEqual(
            Frustum([5, 0, 0], [0, 0, 5], 1, 1).to_scad(),
            ScadObject("translate", [[5, 0, 0]], [], [
                ScadObject("rotate", [], [('a', 45.0),
                                          ('v', [0.0, -1.0, 0.0])],
                           [cylinder_scad])
            ]))