# Involute Gears Generation Script
# by Marcin Wanczyk (dj_who)
# (c) 2011 LGPL


import FreeCAD
import FreeCADGui
import Part
import Draft
import MeshPart
import Mesh
import math
from PySide import QtGui, QtCore

App = FreeCAD
Gui = FreeCADGui


def proceed():
    try:
        compute()
    except Exception:
        hide()
        QtGui.QApplication.restoreOverrideCursor()


def compute():
    QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)

    if FreeCAD.ActiveDocument is None:
        FreeCAD.newDocument("Gear")

    oldDocumentObjects = App.ActiveDocument.Objects

    try:
        N = int(l1.text())
        p = float(l2.text())
        alfa = int(l3.text())
        y = float(l4.text())       # standard value y<1 for gear drives y>1 for Gear pumps
        m = p/math.pi              # standard value 0.06, 0.12, 0.25, 0.5, 1, 2, 4, 8, 16, 32, 60 (polish norm)
        c = float(l5.text())*m     # standard value 0,1*m - 0,3*m
        j = float(l6.text())*m     # standard value 0,015 - 0,04*m
        width = float(l7.text())   # gear width
    except ValueError:
        FreeCAD.Console.PrintError("Wrong input! Only numbers allowed...\n")


    # tooth height
    h = 2*y*m+c

    # pitch diameter
    d = N*m

    # root diameter
    df = d - 2*y*m - 2*c              # df=d-2hf where and hf=y*m+c

    # addendum diameter
    da = d + 2*y*m                    # da=d+2ha where ha=y*m

    # base diameter for involute
    db = d * math.cos(math.radians(alfa))


    #Base circle
    baseCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "BaseCircle")
    Draft._Circle(baseCircle)
    Draft._ViewProviderDraft(baseCircle.ViewObject)
    baseCircle.Radius = db/2
    baseCircle.FirstAngle = 0.0
    baseCircle.LastAngle = 0.0

    # Root circle
    rootCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "RootCircle")
    Draft._Circle(rootCircle)
    Draft._ViewProviderDraft(rootCircle.ViewObject)
    rootCircle.Radius = df/2
    rootCircle.FirstAngle = 0.0
    rootCircle.LastAngle = 0.0

    # Addendum circle
    addendumCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "AddendumCircle")
    Draft._Circle(addendumCircle)
    Draft._ViewProviderDraft(addendumCircle.ViewObject)
    addendumCircle.Radius = da/2
    addendumCircle.FirstAngle = 0.0
    addendumCircle.LastAngle = 0.0

    # Pitch circle
    pitchCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "PitchCircle")
    Draft._Circle(pitchCircle)
    Draft._ViewProviderDraft(pitchCircle.ViewObject)
    pitchCircle.Radius = d/2
    pitchCircle.FirstAngle = 0.0
    pitchCircle.LastAngle = 0.0

#************ Calculating right sides of teeth
    # Involute of base circle
    involute = []
    involutee = []
    involutesav = []

    for t in range(0, 60, 1):
        x = db/2*(math.cos(math.radians(t))+math.radians(t)*math.sin(math.radians(t)))
        y = db/2*(math.sin(math.radians(t))-math.radians(t)*math.cos(math.radians(t)))
        involute.append(Part.Vertex(x, y, 0).Point)

#************ Drawing right sides of teeth
    involutesav.extend(involute)
    involutee.extend(involute)

    for angle in range(1, N+1, 1):
        involuteobj = FreeCAD.ActiveDocument.addObject("Part::Feature", "InvoluteL" + str(angle))
        involutee.insert(0, (0, 0, 0))
        involuteshape = Part.makePolygon(involutee)
        involuteobj.Shape=involuteshape
        involutee = []
        for num in range(0, 60, 1):
            point = involute.pop()
            pointt = Part.Vertex(point.x*math.cos(math.radians(angle*360/N)) - point.y*math.sin(math.radians(angle*360/N)),point.x*math.sin(math.radians(angle*360/N)) + point.y*math.cos(math.radians(angle*360/N)),0).Point
            involutee.insert(0,pointt)
        involute.extend(involutesav)
    involutee = []

#************ Calculating difference between tooth spacing on BaseCircle and PitchCircle

    pc = App.ActiveDocument.getObject("PitchCircle")
    inv = App.ActiveDocument.getObject("InvoluteL1")
    cut = inv.Shape.cut(pc.Shape)
#    FreeCAD.ActiveDocument.addObject("Part::Feature","CutInv").Shape=cut
    invPoint = cut.Vertexes[0].Point

    diff = invPoint.y*2       # instead of making axial symmetry and calculating point distance.
    anglediff = 2*math.asin(diff/d)

#************ Calculating left sides of teeth

#************ Inversing Involute
    for num in range(0, 60, 1):
        point = involute.pop()
        pointt = Part.Vertex(point.x, point.y*-1, 0).Point
        involutee.insert(0, pointt)
    involute.extend(involutee)
    involutee = []

#Normal tooth size calculated as: 0,5*  p    -      j                       j = m * 0,1 below are calculations
#                                 0,5*  p    -      m      * 0,1
#                                 0,5*  p    -     p   /pi * 0,1
#                                 0,5*360/N  - ((360/N)/pi)* 0,1
#                                 0,5*360/N  - (360/N)*((1/pi)*0,1)         j = (p/pi)*0,1
#                                 0,5*360/N  - (360/N)*((p/pi)*0,1)/p
#                                 0,5*360/N  - (360/N)*(    j     )/p
    for num in range(0, 60, 1):
        point = involute.pop()
        pointt = Part.Vertex(point.x*math.cos(math.radians(180/N-(360/N)*(j/p))+anglediff) - point.y*math.sin(math.radians(180/N-(360/N)*(j/p))+anglediff),point.x*math.sin(math.radians(180/N-(360/N)*(j/p))+anglediff) + point.y*math.cos(math.radians(180/N-(360/N)*(j/p))+anglediff),0).Point
        involutee.insert(0, pointt)
    involute.extend(involutee)
    involutesav = []
    involutesav.extend(involute)

#************ Drawing left sides of teeth
    for angle in range(1, N+1, 1):
        involuteobj = FreeCAD.ActiveDocument.addObject("Part::Feature", "InvoluteR" + str(angle))
        involutee.insert(0, (0, 0, 0))
        involuteshape = Part.makePolygon(involutee)
        involuteobj.Shape = involuteshape
        involutee = []
        for num in range(0,60,1):
            point = involute.pop()
            pointt = Part.Vertex(point.x*math.cos(math.radians(angle*360/N)) - point.y*math.sin(math.radians(angle*360/N)),point.x*math.sin(math.radians(angle*360/N)) + point.y*math.cos(math.radians(angle*360/N)),0).Point
            involutee.insert(0,pointt)
        involute.extend(involutesav)

    Gui.SendMsgToActiveView("ViewFit")

#************ Forming teeth

    cutCircle = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutCircle")
    Draft._Circle(cutCircle)
    Draft._ViewProviderDraft(cutCircle.ViewObject)
    cutCircle.Radius = da    # da because must be bigger than addendumCircle and bigger than whole construction da is right for this but it not has to be.
    cutCircle.FirstAngle = 0.0
    cutCircle.LastAngle = 0.0


    cutTool = cutCircle.Shape.cut(addendumCircle.Shape)
    # cutshape = Part.show(cutTool)

    gearShape = rootCircle.Shape

    for invNum in range(1, N+1, 1):
        invL = App.ActiveDocument.getObject("InvoluteL" + str(invNum))
        invR = App.ActiveDocument.getObject("InvoluteR" + str(invNum))
        cutL = invL.Shape.cut(cutTool)
        cutR = invR.Shape.cut(cutTool)
        pointL = cutL.Vertexes.pop().Point
        pointR = cutR.Vertexes.pop().Point
        faceEdge = Part.makeLine(pointL, pointR)

        toothWhole = cutL.fuse(cutR)
        toothWhole = toothWhole.fuse(faceEdge)
        toothWire = Part.Wire(toothWhole.Edges)
        toothShape = Part.Face(toothWire)
#        tooth = App.ActiveDocument.addObject("Part::Feature", "Tooth" +str(invNum))
#        tooth.Shape=toothShape
        gearShape = gearShape.fuse(toothShape)


    for o in App.ActiveDocument.Objects:
        if oldDocumentObjects.count(o) == 0:
            App.ActiveDocument.removeObject(o.Name)

    gearFlat = App.ActiveDocument.addObject("Part::Feature", "GearFlat")
    gearFlat.Shape = gearShape
    Gui.ActiveDocument.getObject(gearFlat.Name).Visibility = False

    gear = App.ActiveDocument.addObject("Part::Extrusion", "Gear3D")
    gear.Base = gearFlat
    gear.Dir = (0, 0, width)
    App.ActiveDocument.recompute()


    if c1.isChecked()==True:
        gearMesh = App.ActiveDocument.addObject("Mesh::Feature", "Gear3D-mesh")

        faces = []
        triangles = gear.Shape.tessellate(1)  # the number represents the precision of the tessellation)
        for tri in triangles[1]:
            face = []
            for i in range(3):
                vindex = tri[i]
                face.append(triangles[0][vindex])
            faces.append(face)
        mesh = Mesh.Mesh(faces)

        gearMesh.Mesh = mesh
        App.ActiveDocument.removeObject(gear.Name)
        App.ActiveDocument.removeObject(gearFlat.Name)


    App.ActiveDocument.recompute()
    Gui.SendMsgToActiveView("ViewFit")

    QtGui.QApplication.restoreOverrideCursor()


    hide()

def hide():
    dialog.hide()

dialog = QtGui.QDialog()
dialog.resize(200,450)
dialog.setWindowTitle("Gear")
la = QtGui.QVBoxLayout(dialog)
t1 = QtGui.QLabel("Number of teeth (N)")
la.addWidget(t1)
l1 = QtGui.QLineEdit()
l1.setText("16")
la.addWidget(l1)
t2 = QtGui.QLabel("Circular pitch (p)")
la.addWidget(t2)
l2 = QtGui.QLineEdit()
l2.setText("1.65")
la.addWidget(l2)
t3 = QtGui.QLabel("Pressure angle (alfa)")
la.addWidget(t3)
l3 = QtGui.QLineEdit()
l3.setText("20")
la.addWidget(l3)
t4 = QtGui.QLabel("Tooth height factor (y)")
la.addWidget(t4)
l4 = QtGui.QLineEdit()
l4.setText("1.0")
la.addWidget(l4)
t5 = QtGui.QLabel("Tooth clearance (c)")
la.addWidget(t5)
l5 = QtGui.QLineEdit()
l5.setText("0.1")
la.addWidget(l5)
t6 = QtGui.QLabel("Tooth lateral clearance (j)")
la.addWidget(t6)
l6 = QtGui.QLineEdit()
l6.setText("0.04")
la.addWidget(l6)
t7 = QtGui.QLabel("Gear width")
la.addWidget(t7)
l7 = QtGui.QLineEdit()
l7.setText("6.0")
la.addWidget(l7)
c1 = QtGui.QCheckBox("Create as a Mesh")
la.addWidget(c1)
e1 = QtGui.QLabel("(for faster rendering)")
commentFont = QtGui.QFont("Times", 8, True)
e1.setFont(commentFont)
la.addWidget(e1)

okbox = QtGui.QDialogButtonBox(dialog)
okbox.setOrientation(QtCore.Qt.Horizontal)
okbox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
la.addWidget(okbox)
QtCore.QObject.connect(okbox, QtCore.SIGNAL("accepted()"), proceed)
QtCore.QObject.connect(okbox, QtCore.SIGNAL("rejected()"), hide)
QtCore.QMetaObject.connectSlotsByName(dialog)
dialog.show()
