text: #system modules
  from __future__ import division
  import sys
  import unittest
  from math import pi, sin, sqrt, radians
  from tests import BaseTest
  import FreeCAD
  import Part
  from copy import copy
  
  from cadquery import *
  
  class TestCadObjects(BaseTest):
  
      def testVectorConstructors(self):
          # Assert 3 ints represents x, y, z respectively
          #   (why?, this is assumed for all other assertions)
          v = Vector(1, 2, 3)
          self.assertEqual((v.x, v.y, v.z), (1, 2, 3))
  
          # --- Positive Cases
          # empty
          self.assertEquals(Vector(), Vector(0, 0, 0))
          # tuples
          self.assertEquals(Vector((1, 2, 3)), Vector(1, 2, 3))
          self.assertEquals(Vector((1, 2)), Vector(1, 2, 0))
          self.assertEquals(Vector((1,)), Vector(1, 0, 0))
          # lists
          self.assertEquals(Vector([1, 2, 3]), Vector(1, 2, 3))
          self.assertEquals(Vector([1, 2]), Vector(1, 2, 0))
          self.assertEquals(Vector([1]), Vector(1, 0, 0))
          # < 3 numbers
          self.assertEquals(Vector(1, 2), Vector(1, 2, 0))
          self.assertEquals(Vector(1), Vector(1, 0, 0))
          # wrappers
          self.assertEquals(Vector(Vector(1, 2, 3)), Vector(1, 2, 3))
          self.assertEquals(Vector(FreeCAD.Base.Vector(1, 2, 3)), Vector(1, 2, 3))
          # named coords
          self.assertEquals(Vector(x=1, y=2, z=3), Vector(1, 2, 3))
          self.assertEquals(Vector(x=1), Vector(1, 0, 0))
          self.assertEquals(Vector(y=2), Vector(0, 2, 0))
          self.assertEquals(Vector(z=3), Vector(0, 0, 3))
  
          # --- Negative Cases
          with self.assertRaises(ValueError):
              Vector('blah')  # invalid type
          with self.assertRaises(ValueError):
              Vector(1, 2, 3, 4)
          with self.assertRaises(ValueError):
              Vector((1, 2, 3, 4))
          with self.assertRaises(ValueError):
              Vector([1, 2, 3, 4])
          with self.assertRaises(ValueError):
              # mixing listed and named args not supported
              Vector(1, 2, z=3)
          with self.assertRaises(ValueError):
              # non-numeric as first parameter
              Vector(FreeCAD.Base.Vector(1, 2, 3), 1)
  
      def testVectorCopy(self):
          a = Vector(1, 2, 3)
          b = copy(a)
          # assert copy is equal
          self.assertEqual(a.toTuple(), (1, 2, 3))
          self.assertEqual(b.toTuple(), (1, 2, 3))
          # assert changes to original don't effect copy
          a.x = 100
          self.assertEqual(a.toTuple(), (100, 2, 3))
          self.assertEqual(b.toTuple(), (1, 2, 3))
  
      def testVertex(self):
          """
              Tests basic vertex functions
          """
          # Tests casting a vertex
          vc = Vertex.cast(Part.Vertex(1, 1, 1))
          self.assertEqual(1, vc.X)
          self.assertEqual(Vector, type(vc.Center()))
  
          # Tests vertex instantiation
          v = Vertex(Part.Vertex(1, 1, 1))
          self.assertEqual(1, v.X)
          self.assertEqual(Vector, type(v.Center()))
  
      def testFace(self):
          """
          Test basic face functions, cast and instantiation
          """
          edge1 = Part.makeLine((0, 0, 0), (0, 10, 0))
          edge2 = Part.makeLine((0, 10, 0), (10, 10, 0))
          edge3 = Part.makeLine((10, 10, 0), (10, 0, 0))
          edge4 = Part.makeLine((10, 0, 0), (0, 0, 0))
          wire1 = Part.Wire([edge1,edge2,edge3,edge4])
          face1 = Part.Face(wire1)
  
          mplanec = Face.cast(face1)
          mplane = Face(face1)
  
          self.assertTupleAlmostEquals((5.0, 5.0, 0.0), mplane.Center().toTuple(), 3)
  
      def testShapeProps(self):
          """
          Tests miscellaneous properties of the shape object
          """
          e = Shape(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
  
          # Geometry type
          self.assertEqual(e.geomType(), 'Edge')
  
          # Dynamic type checking
          self.assertTrue(e.isType(e, 'Edge'))
          self.assertFalse(e.isType(None, 'Edge'))
  
          # Checking null objects
          self.assertFalse(e.isNull())
  
          # Checking for sameness
          self.assertTrue(e.isSame(e))
  
          # Checking for equality
          self.assertTrue(e.isEqual(e))
  
          # Checking for shape validity
          self.assertTrue(e.isValid())
  
          # Testing whether shape is closed
          self.assertTrue(e.Closed())
  
          # Trying to get the area of the circular edge
          with self.assertRaises(ValueError):
              e.Area()
  
          # Getting the area of the square face
          mplane = Face.makePlane(10.0, 10.0)
          self.assertAlmostEqual(100.0, mplane.Area(), 3)
  
          # Getting the center of a solid
          s = Solid.makeCylinder(10.0, 10.0)
          self.assertTupleAlmostEquals((0.0, 0.0, 5.0), s.Center().toTuple(), 3)
  
      def testBasicBoundingBox(self):
          v = Vertex(Part.Vertex(1, 1, 1))
          v2 = Vertex(Part.Vertex(2, 2, 2))
          self.assertEqual(BoundBox, type(v.BoundingBox()))
          self.assertEqual(BoundBox, type(v2.BoundingBox()))
  
          bb1 = v.BoundingBox().add(v2.BoundingBox())
  
          self.assertEqual(bb1.xlen, 1.0)
  
      def testEdgeWrapperCenter(self):
          e = Edge(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
  
          self.assertTupleAlmostEquals((1.0, 2.0, 3.0), e.Center().toTuple(), 3)
  
      def testEdgeWrapperMakeCircle(self):
          halfCircleEdge = Edge.makeCircle(radius=10, pnt=(0, 0, 0), dir=(0, 0, 1), angle1=0, angle2=180)
  
          self.assertTupleAlmostEquals((0.0, 5.0, 0.0), halfCircleEdge.CenterOfBoundBox(0.0001).toTuple(),3)
          self.assertTupleAlmostEquals((10.0, 0.0, 0.0), halfCircleEdge.startPoint().toTuple(), 3)
          self.assertTupleAlmostEquals((-10.0, 0.0, 0.0), halfCircleEdge.endPoint().toTuple(), 3)
  
      def testFaceWrapperMakePlane(self):
          mplane = Face.makePlane(10,10)
  
          self.assertTupleAlmostEquals((0.0, 0.0, 1.0), mplane.normalAt().toTuple(), 3)
  
      def testCenterOfBoundBox(self):
          """
          Tests whether or not a proper geometric center can be found for an object
          """
          def cylinders(self, radius, height):
              def _cyl(pnt):
                  # Inner function to build a cylinder
                  return Solid.makeCylinder(radius, height, pnt)
  
              # Combine all the cylinders into a single compound
              r = self.eachpoint(_cyl, True).combineSolids()
  
              return r
  
          Workplane.cyl = cylinders
  
          # One solid in the compound
          s = Workplane("XY").pushPoints([(0.0, 0.0, 0.0)]).cyl(0.25, 0.5)
          self.assertEqual(1, len(s.val().Solids()))
          self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().CenterOfBoundBox().toTuple(), 2)
  
      def testCombinedCenterOfBoundBox(self):
          """
          Tests whether or not a proper geometric center can be found for multiple
          objects in a compound.
          """
          def cylinders(self, radius, height):
              def _cyl(pnt):
                  # Inner function to build a cylinder
                  return Solid.makeCylinder(radius, height, pnt)
  
              # Combine all the cylinders into a single compound
              r = self.eachpoint(_cyl, True).combineSolids()
  
              return r
  
          Workplane.cyl = cylinders
  
          # Multiple solids in the compound
          s = Workplane("XY").rect(2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
          self.assertEqual(4, len(s.val().Solids()))
          self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().CenterOfBoundBox().toTuple(), 2)
  
      def testCenter(self):
          """
          Tests finding the center of shapes and solids
          """
          circle = Workplane("XY").circle(10.0)
          cylinder = circle.extrude(10.0)
  
          self.assertTupleAlmostEquals((0.0, 0.0, 0.0), circle.val().Center().toTuple(), 3)
          self.assertTupleAlmostEquals((0.0, 0.0, 5.0), cylinder.val().Center().toTuple(), 3)
  
      def testCompoundCenter(self):
          """
          Tests whether or not a proper weighted center can be found for a compound
          """
          def cylinders(self, radius, height):
              def _cyl(pnt):
                  # Inner function to build a cylinder
                  return Solid.makeCylinder(radius, height, pnt)
  
              # Combine all the cylinders into a single compound
              r = self.eachpoint(_cyl, True).combineSolids()
  
              return r
  
          Workplane.cyl = cylinders
  
          # Now test. here we want weird workplane to see if the objects are transformed right
          s = Workplane("XY").rect(2.0, 3.0, forConstruction=True).vertices().cyl(0.25, 0.5)
  
          self.assertEqual(4, len(s.val().Solids()))
          self.assertTupleAlmostEquals((0.0, 0.0, 0.25), s.val().Center().toTuple(), 3)
  
      def testDot(self):
          v1 = Vector(2, 2, 2)
          v2 = Vector(1, -1, 1)
          self.assertEqual(2.0, v1.dot(v2))
  
      def testVectorAdd(self):
          result = Vector(1, 2, 0) + Vector(0, 0, 3)
          self.assertIsInstance(result, Vector)
          self.assertTupleAlmostEquals((1.0, 2.0, 3.0), result.toTuple(), 3)
  
      def testVectorArithmeticOverides(self):
          V = lambda x,y,z: Vector(x,y,z)
          self.assertEqual(V(1,2,3) + V(4,5,6), V(5,7,9))  # addition
          self.assertEqual(V(1,2,3) - V(5,4,3), V(-4,-2,0))  # subtraction
          self.assertEqual((V(1,2,3) * 2).toTuple(), (2,4,6))  # multiplication
          self.assertEqual((V(1,2,3) / 2).toTuple(), (0.5,1,1.5))  # division
  
      def testVectorBoolCast(self):
          # zero vector
          self.assertEqual(bool(Vector(0,0,0)), False)
          # positive axes
          self.assertEqual(bool(Vector(1,0,0)), True)
          self.assertEqual(bool(Vector(0,1,0)), True)
          self.assertEqual(bool(Vector(0,0,1)), True)
          # negative axes
          self.assertEqual(bool(Vector(-1,0,0)), True)
          self.assertEqual(bool(Vector(0,-1,0)), True)
          self.assertEqual(bool(Vector(0,0,-1)), True)
  
      def testVectorDivideByZero(self):
          with self.assertRaises(ZeroDivisionError):
              Vector(1, 2, 3) / 0
  
      def testVectorLength(self):
          calc_length = lambda v: sqrt(v.x**2 + v.y**2 + v.z**2)
          vectors = [
              Vector(0,0,0), Vector(1,2,3), Vector(-1,-5,10),
          ]
          for v in vectors:
              expected = calc_length(v)
              self.assertEqual(v.Length, expected)
              self.assertEqual(abs(v), expected)
  
      def testVectorSub(self):
          result = Vector(1, 2, 3) - Vector(6, 5, 4)
          self.assertIsInstance(result, Vector)
          self.assertTupleAlmostEquals((-5, -3, -1), result.toTuple())
  
      def testVectorEquality(self):
          v1 = Vector(1, 2, 3)
          v2 = Vector(1, 2, 3)  # same value as v1, different id
          v3 = Vector(1, 2, 4)
          self.assertEqual(v1 == v2, True)
          self.assertEqual(v1 != v2, False)
          self.assertEqual(v1 == v3, False)
          self.assertEqual(v1 != v3, True)
  
      def testVectorCoords(self):
          (x, y, z) = (1, 2, 3)
          v = Vector(x, y, z)
          for (coord, init_val) in (('x', x), ('y', y), ('z', z)):
              new_val = init_val + 10
              self.assertEqual(getattr(v, coord), init_val)
              setattr(v, coord, new_val)
              self.assertEqual(getattr(v, coord), new_val)
              setattr(v, coord, init_val)
  
      def testVectorNegative(self):
          v = Vector(1, -2, 3)
          self.assertEqual(-v, Vector(-1, 2, -3))
  
      def testShapeInit(self):
          """
          Tests whether a Shape object can be instantiated without
          throwing an error.
          """
          e = Shape(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
  
      def testPlaneEqual(self):
          # default orientation
          self.assertEqual(
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1))
          )
          # moved origin
          self.assertEqual(
              Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1)),
              Plane(origin=(2,1,-1), xDir=(1,0,0), normal=(0,0,1))
          )
          # moved x-axis
          self.assertEqual(
              Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1)),
              Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
          )
          # moved z-axis
          self.assertEqual(
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1)),
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
          )
  
      def testPlaneNotEqual(self):
          # type difference
          for value in [None, 0, 1, 'abc']:
              self.assertNotEqual(
                  Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
                  value
              )
          # origin difference
          self.assertNotEqual(
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
              Plane(origin=(0,0,1), xDir=(1,0,0), normal=(0,0,1))
          )
          # x-axis difference
          self.assertNotEqual(
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
              Plane(origin=(0,0,0), xDir=(1,1,0), normal=(0,0,1))
          )
          # z-axis difference
          self.assertNotEqual(
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,0,1)),
              Plane(origin=(0,0,0), xDir=(1,0,0), normal=(0,1,1))
          )
  
      def testTranslate(self):
          e = Shape.cast(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
          e2 = e.translate(Vector(0, 0, 1))
  
          self.assertTupleAlmostEquals((1.0, 2.0, 4.0), e2.Center().toTuple(), 3)
  
      def testScale(self):
          """
          Tests scaling a shape and whether the dimensions are correct afterwards
          """
          e = Shape.cast(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
          e2 = e.scale(0.5)
  
          self.assertAlmostEquals(2.0, e2.BoundingBox(tolerance=0.0001).xlen, 3)
          self.assertAlmostEquals(2.0, e2.BoundingBox(tolerance=0.0001).ylen, 3)
  
      def testCopy(self):
          """
          Tests making a copy of a shape object and whether the new one has the
          same properties as the original.
          """
          e = Shape.cast(Part.makeCircle(2.0, FreeCAD.Base.Vector(1, 2, 3)))
          e2 = e.copy()
  
          self.assertEquals(e.BoundingBox().xlen, e2.BoundingBox().xlen)
          self.assertEquals(e.BoundingBox().ylen, e2.BoundingBox().ylen)
  
      def testRuledSurface(self):
          """
          Tests making a ruled surface from two edges/wires.
          """
          edge1 = Shape(Part.makeLine((0, 0, 5), (0, 10, 5)))
          edge2 = Shape(Part.makeLine((5, 5, 0), (10, 10, 0)))
  
          surf1 = Face.makeRuledSurface(edge1, edge2)
  
          self.assertEquals(surf1.ShapeType(), 'Face')
          self.assertTrue(surf1.isValid())
  
      def testCut(self):
          """
          Tests cutting one face from another.
          """
          # Face 1
          edge1 = Part.makeLine((0, 0, 0), (0, 10, 0))
          edge2 = Part.makeLine((0, 10, 0), (10, 10, 0))
          edge3 = Part.makeLine((10, 10, 0), (10, 0, 0))
          edge4 = Part.makeLine((10, 0, 0), (0, 0, 0))
          wire1 = Part.Wire([edge1,edge2,edge3,edge4])
          face1 = Part.Face(wire1)
          cqFace1 = Face(face1)
  
          # Face 2 (face to cut out of face 1)
          edge1 = Part.makeLine((0, 0, 0), (0, 5, 0))
          edge2 = Part.makeLine((0, 5, 0), (5, 5, 0))
          edge3 = Part.makeLine((5, 5, 0), (5, 0, 0))
          edge4 = Part.makeLine((5, 0, 0), (0, 0, 0))
          wire1 = Part.Wire([edge1,edge2,edge3,edge4])
          face2 = Part.Face(wire1)
          cqFace2 = Face(face2)
  
          # Face resulting from cut
          cqFace3 = cqFace1.cut(cqFace2)
  
          self.assertEquals(len(cqFace3.Faces()), 1)
          self.assertEquals(len(cqFace3.Edges()), 6)
  
      def testFuse(self):
          """
          Tests fusing one face to another.
          """
          # Face 1
          edge1 = Part.makeLine((0, 0, 0), (0, 10, 0))
          edge2 = Part.makeLine((0, 10, 0), (10, 10, 0))
          edge3 = Part.makeLine((10, 10, 0), (10, 0, 0))
          edge4 = Part.makeLine((10, 0, 0), (0, 0, 0))
          wire1 = Part.Wire([edge1,edge2,edge3,edge4])
          face1 = Part.Face(wire1)
          cqFace1 = Face(face1)
  
          # Face 2 (face to cut out of face 1)
          edge1 = Part.makeCircle(4.0)
          wire1 = Part.Wire([edge1])
          face2 = Part.Face(wire1)
          cqFace2 = Face(face2)
  
          # Face resulting from fuse
          cqFace3 = cqFace1.fuse(cqFace2)
  
          self.assertEquals(len(cqFace3.Faces()), 3)
          self.assertEquals(len(cqFace3.Edges()), 8)
  
      def testIntersect(self):
          """
          Tests finding the intersection of two faces.
          """
          # Face 1
          edge1 = Part.makeLine((0, 0, 0), (0, 10, 0))
          edge2 = Part.makeLine((0, 10, 0), (10, 10, 0))
          edge3 = Part.makeLine((10, 10, 0), (10, 0, 0))
          edge4 = Part.makeLine((10, 0, 0), (0, 0, 0))
          wire1 = Part.Wire([edge1,edge2,edge3,edge4])
          face1 = Part.Face(wire1)
          cqFace1 = Face(face1)
  
          # Face 2 (face to cut out of face 1)
          edge1 = Part.makeCircle(4.0)
          wire1 = Part.Wire([edge1])
          face2 = Part.Face(wire1)
          cqFace2 = Face(face2)
  
          # Face resulting from the intersection
          cqFace3 = cqFace1.intersect(cqFace2)
  
          self.assertEquals(len(cqFace3.Faces()), 1)
          self.assertEquals(len(cqFace3.Edges()), 3)
  
      def testVertices(self):
          e = Shape.cast(Part.makeLine((0, 0, 0), (1, 1, 0)))
          self.assertEqual(2, len(e.Vertices()))
  
      def testWireMakeHelixDefault(self):
          (pitch, height, radius) = (1., 5., 2.)
          wire = Wire.makeHelix(pitch=pitch, height=height, radius=radius)
  
          edge = wire.Edges()[0]
          # Assert: helix length is correct
          # expectation, default is a cylindrical helix
          helix_horiz = (((2 * pi) * radius) * (height / pitch))
          helix_vert = height
          self.assertAlmostEqual(edge.Length(), sqrt(helix_horiz**2 + helix_vert**2), 4)
  
          # Assert: bounding box is accurate
          #   mainly checking that helix is in the positive Z direction.
          #   not happy with the accuracy of BoundingBox (see places=2 below), but that's out of cadquery's scope
          bb = edge.BoundingBox()
          self.assertTupleAlmostEquals((bb.xmin, bb.xmax), (-radius, radius), 2)
          self.assertTupleAlmostEquals((bb.ymin, bb.ymax), (-radius, radius), 2)
          self.assertTupleAlmostEquals((bb.zmin, bb.zmax), (0, height), 3)
  
      def testWireMakeHelixConical(self):
          # helix is an upside-down cone
          #   - beginning with a radius of `radius`
          #   - ending with a radius of `radius + height*sin(30)`
          (pitch, height, radius, angle) = (0.1, 5., 2., 30.)
          wire = Wire.makeHelix(
              pitch=pitch, height=height, radius=radius,
              angle=angle, lefthand=True, heightstyle=True,
          )  # left hand, just for goood measure
  
          edge = wire.Edges()[0]
          # Assert: bounding box is accurate
          # note: small pitch increases accuracy of bounding box, but it's still atrocious
          bb = edge.BoundingBox()
          end_radius = radius + height * sin(radians(angle))
          self.assertTupleAlmostEquals((bb.xmin, bb.xmax), (-end_radius, end_radius), 0)
          self.assertTupleAlmostEquals((bb.ymin, bb.ymax), (-end_radius, end_radius), 0)
          self.assertTupleAlmostEquals((bb.zmin, bb.zmax), (0, height), 3)
  
  
  if __name__ == '__main__':
      unittest.main()
  