#******************************************************************************
#
# Assembly animation by Joakim Isaksson 20140808
#
#******************************************************************************
#
# USAGE:
#  Open your CAD assembly file and zoom and orient the model as desired.
#  Hide allla items in the file.
#  Edit-configuration in the script so it conforms to your model an computer.
#  To quickly test your animation, comment out large amounts of your parts.
#  The folder that mp4 file and png images end up in must be emptied
#   manually between each run of the script.
#  Activate your CAD documents and run the script.
#  Go out and get some sun and fresh air.
#
# WARNING!
#  The script is only limited tested on:
#    OS: Debian GNU / Linux 7.6 (wheezy)
#    FreeCAD Version: 0.15.3829 (Git) Branch: master
#  Do not save your CAD assembly file after the script is run.
#  There is no function to abort the script.
#    On GNU / Linux use [ kill -9 (process number "PID" ]
#     to kill avconv AND/OR FreeCAD. killalll may not work!
#
#******************************************************************************

import FreeCAD, FreeCADGui, Draft, Part
from FreeCAD import Gui
import math
from pivy import coin
from pivy import sogui
from PySide import QtCore, QtGui
from time import sleep
import subprocess
import os

#******************************************************************************
# + + + + + + + + + + + + BEGIN Animation configuration + + + + + + + + + + + +

doc_name = "_001_0006"         # The document name of the FreeCAD model
image_path = "F:\\temp\\freecad_animation\\output\\"   # Set path to your requirements.
ani_startwait = 2000      # Milliseconds before the animation starting
ani_partwait   = 20      # Milliseconds before the part begin to move
ani_bitrate = 10         # Milliseconds between part moves
ani_dist = 2            # Distance part moves each time
ani_offset_dist = 80      # Distance part is moved before the animation starts

# Tuples for all parts label
#  *  First item in Tuples is the animation view angle
#  *  The remaining items are label on parts to be assembled in list sequence
ani_1  = ( "z-" , "PCB_PCB" , "ImagePlane_Bot" ) ;
ani_2  = ( "z+" , "ImagePlane_Top"  ) ;
ani_3  = ( "z+" , "U1" , "U2" ,
           "C1", "C2", "Q1", "R1", "R2", "R3", "R4", "JP1", "JP2", "JP3", "JP4", "Top" ) ;
ani_4  = ( "z-" , "Bottom" , "Bolt1" ,
           "Bolt2" , "Bolt3" , "Bolt4" ,
           "Bat_Contact_Plus" , "Bat_Contact_Minus" , "Bat_Contact_Med" ,
           "Bat1" , "Bat2" ) ;
#	ani_3  = ( "z+" , "ISO4762_M4x12_011" , "ISO4762_M4x12_012" ,
#	           "ISO4762_M4x12_013" , "ISO4762_M4x12_014" , "ISO4762_M4x12_015" ,
#	           "ISO4762_M4x12_016" , "ISO4762_M4x12_017" , "ISO4762_M4x12_018" ,
#	           "ISO4762_M4x12_019" , "ISO4762_M4x12_020" ) ;
#	ani_4  = ( "z+" , "EuroCard_001" , "EuroCard_002" , "EuroCard_003" ,
#	           "EuroCard_004" ) ;
#	ani_5  = ( "z+" , "Panel_005" ,  "Panel_006" , "Panel_007" , "Panel_008" ) ;
#	ani_6  = ( "z+" , "ISO7380_M3x8_" , "ISO7380_M3x8_001" , "ISO7380_M3x8_002" ,
#	           "ISO7380_M3x8_003" , "ISO7380_M3x8_004" , "ISO7380_M3x8_005" ,
#	           "ISO7380_M3x8_006" , "ISO7380_M3x8_007" , "ISO7380_M3x8_008" ,
#	           "ISO7380_M3x8_009" ) ;
#	ani_7  = ( "z-" , "Panel_001" , "Panel_002" , "Panel_003" , "Panel_004" ) ;
#	ani_8  = ( "z-" , "ISO7380_M3x8_010" , "ISO7380_M3x8_011" ,
#	           "ISO7380_M3x8_012" , "ISO7380_M3x8_013" , "ISO7380_M3x8_014" ,
#	           "ISO7380_M3x8_015" , "ISO7380_M3x8_016" , "ISO7380_M3x8_017" ,
#	           "ISO7380_M3x8_018" , "ISO7380_M3x8_019" ) ;
#	ani_9  = ( "y-" , "LidBottom_001" ) ;
#	ani_10 = ( "y-" , "ISO10642_M3x8_" , "ISO10642_M3x8_001" ,
#	           "ISO10642_M3x8_002" , "ISO10642_M3x8_003" , "ISO10642_M3x8_004" ,
#	           "ISO10642_M3x8_005" , "ISO10642_M3x8_006" , "ISO10642_M3x8_007" ,
#	           "ISO10642_M3x8_008" , "ISO10642_M3x8_009" ) ;
#	ani_11 = ( "y+" , "LidTop_001" ) ;
ani_12 = ( "y-" , "Battery_Cover" ) ;

# Set the animation end view
#ani_end = "z+"
ani_end = "x-"

# Camera oriantations
#  *  Edit this tuples to your need
rear_view_pos    = ( -400 , -80 , 480 ) ;
rear_view_ang    = ( 0 , 1 , 0  ) ;
front_view_pos    = ( -400 , 80 , 480 ) ;
front_view_ang    = ( 0 , -1 , 0  ) ;
top_view_pos    = ( -400 , -80 , 480  ) ;
top_view_ang    = ( -1 , 0 , 0 ) ;
bottom_view_pos = ( -400 , 80 , -480 );
bottom_view_ang = ( 1 , 0 , 0 ) ;

# + + + + + + + + + + + + END Animation configuration + + + + + + + + + + + +

# + + + + + + + + + + + +       Global variables      + + + + + + + + + + + +
image_prefix = "e"
image_sufix = ".png"
image_count = 0
image_id = "00000"

# + + + + + + + + + + + +           Function          + + + + + + + + + + + +

def imgDump(update_gui):
   global image_prefix
   global image_sufix
   global image_count
   global s
   global img
   global image_path
   image_count += 1
   if len(str(image_count)) == 1:
      image_id = "0000" + str(image_count)
   elif len(str(image_count)) == 2:
      image_id = "000" + str(image_count)
   elif len(str(image_count)) == 3:
      image_id = "00" + str(image_count)
   elif len(str(image_count)) == 4:
      image_id = "0" + str(image_count)
   elif len(str(image_count)) == 5:
      image_id = str(image_count)
   elif len(str(image_count)) > 5:
      App.Console.PrintMessage("ERROR Image count: " \
           + str(image_count) + " To many files !! " + "\n")
      return;
   ani_image = image_path + "/" + image_prefix + image_id + image_sufix
   if update_gui == True:
      Gui.updateGui( )
      sleep(0.05)
      #
      #Gui.ActiveDocument.ActiveView.setAnimationEnabled(False)
      #
   Gui.ActiveDocument.ActiveView.saveImage(ani_image,s[0],s[1])
   img.filename.setValue(ani_image)
   sleep(0.05)

def setView(axis_vector, rotation_vector):
   r=App.Rotation(App.Vector(axis_vector),App.Vector(rotation_vector))
   Gui.ActiveDocument.ActiveView.setCameraOrientation(r.Q)

def getPartName(label):
   for i in App.ActiveDocument.Objects:
      if str(i.Label) == label:
         return str(i.Name)

def getBlankScreen():
   # A bundle of blank screenshots.
   for i in range(1,25):
      imgDump(False)

def animate(aniList):
   total = len(aniList)
   for n in range(1,total):
      direction = aniList[0]
      part_name = getPartName(aniList[n])
      tmpX = App.getDocument(doc_name).getObject(part_name).Placement.Base[0]
      tmpY = App.getDocument(doc_name).getObject(part_name).Placement.Base[1]
      tmpZ = App.getDocument(doc_name).getObject(part_name).Placement.Base[2]
      tmpRotation=App.ActiveDocument.getObject(part_name).Placement.Rotation
      #Show the part
      if direction == "x+":   #TODO not tested
         setView(rear_view_pos , rear_view_ang)
         tmpDist = tmpX + ani_offset_dist
         sel = FreeCADGui.Selection.clearSelection (doc_name )
         App.ActiveDocument.getObject(part_name).Placement=App.Placement\
               (App.Vector(tmpX,tmpDist,tmpZ), App.Rotation(tmpRotation),\
                App.Vector(0,0,0))
         Gui.getDocument(doc_name).getObject(part_name).Visibility=True

         sel = FreeCADGui.Selection.clearSelection (doc_name )

         imgDump(True)
         
         for i in range(0,ani_step):
            tmpDist = tmpDist - ani_dist
            App.ActiveDocument.getObject(part_name).Placement=\
                   App.Placement(App.Vector(tmpX,tmpDist,tmpZ),\
                   App.Rotation(tmpRotation), App.Vector(0,0,0))
            imgDump(True)

      elif direction == "x-":   #TODO not tested
         setView(front_view_pos , front_view_ang)
         tmpDist = tmpX - ani_offset_dist
         sel = FreeCADGui.Selection.clearSelection (doc_name )
         App.ActiveDocument.getObject(part_name).Placement=App.Placement\
               (App.Vector(tmpX,tmpDist,tmpZ),\
               App.Rotation(tmpRotation), App.Vector(0,0,0))
         Gui.getDocument(doc_name).getObject(part_name).Visibility=True

         sel = FreeCADGui.Selection.clearSelection (doc_name )

         imgDump(True)
         
         for i in range(0,ani_step):
            tmpDist = tmpDist + ani_dist
            App.ActiveDocument.getObject(part_name).Placement=App.\
                   Placement(App.Vector(tmpX,tmpDist,tmpZ),\
                   App.Rotation(tmpRotation), App.Vector(0,0,0))
            imgDump(True)

      elif direction == "y+":
         setView(rear_view_pos , rear_view_ang)
         tmpDist = tmpY + ani_offset_dist
         sel = FreeCADGui.Selection.clearSelection (doc_name )
         App.ActiveDocument.getObject(part_name).Placement=App.Placement\
               (App.Vector(tmpX,tmpDist,tmpZ), App.Rotation(tmpRotation),\
               App.Vector(0,0,0))
         Gui.getDocument(doc_name).getObject(part_name).Visibility=True

         sel = FreeCADGui.Selection.clearSelection (doc_name )

         imgDump(True)
         
         for i in range(0,ani_step):
            tmpDist = tmpDist - ani_dist
            App.ActiveDocument.getObject(part_name).Placement=App.\
                   Placement(App.Vector(tmpX,tmpDist,tmpZ),\
                   App.Rotation(tmpRotation),App.Vector(0,0,0))
            imgDump(True)

      elif direction == "y-":
         setView(front_view_pos , front_view_ang)

#         setView(rear_view_pos , rear_view_ang)

         tmpDist = tmpY - ani_offset_dist
         sel = FreeCADGui.Selection.clearSelection (doc_name )
         App.ActiveDocument.getObject(part_name).Placement=App.Placement\
               (App.Vector(tmpX,tmpDist,tmpZ), App.Rotation(tmpRotation),\
               App.Vector(0,0,0))
         Gui.getDocument(doc_name).getObject(part_name).Visibility=True

         sel = FreeCADGui.Selection.clearSelection (doc_name )

         imgDump(True)
         
         for i in range(0,ani_step):
            tmpDist = tmpDist + ani_dist
            App.ActiveDocument.getObject(part_name).Placement=App.\
                   Placement(App.Vector(tmpX,tmpDist,tmpZ),\
                   App.Rotation(tmpRotation), App.Vector(0,0,0))
            imgDump(True)

      elif direction == "z+":
         setView(top_view_pos , top_view_ang)
         tmpDist = tmpZ +ani_offset_dist
         sel = FreeCADGui.Selection.clearSelection (doc_name )
         App.ActiveDocument.getObject(part_name).Placement=App.Placement\
               (App.Vector(tmpX,tmpY,tmpDist), App.Rotation(tmpRotation),\
               App.Vector(0,0,0))
         Gui.getDocument(doc_name).getObject(part_name).Visibility=True

         sel = FreeCADGui.Selection.clearSelection (doc_name )

         imgDump(True)
         
         for i in range(0,ani_step):
            tmpDist = tmpDist - ani_dist
            App.ActiveDocument.getObject(part_name).Placement=App.\
                   Placement(App.Vector(tmpX,tmpY,tmpDist),\
                   App.Rotation(tmpRotation), App.Vector(0,0,0))
            imgDump(True)

      elif direction == "z-":
         setView(bottom_view_pos , bottom_view_ang)
         tmpDist = tmpZ - ani_offset_dist
         sel = FreeCADGui.Selection.clearSelection (doc_name )
         App.ActiveDocument.getObject(part_name).Placement=App.Placement\
               (App.Vector(tmpX,tmpY,tmpDist), App.Rotation(tmpRotation),\
               App.Vector(0,0,0))
         Gui.getDocument(doc_name).getObject(part_name).Visibility=True

         sel = FreeCADGui.Selection.clearSelection (doc_name )

         imgDump(True)
         
         for i in range(0,ani_step):
            tmpDist = tmpDist + ani_dist
            App.ActiveDocument.getObject(part_name).Placement=App.\
                   Placement(App.Vector(tmpX,tmpY,tmpDist),\
                   App.Rotation(tmpRotation), App.Vector(0,0,0))
            imgDump(True)

def theend(direction):
   if direction == "x+":
      setView(rear_view_pos , rear_view_ang)
   elif direction == "x-":
      setView(front_view_pos , front_view_ang)
   elif direction == "y+":
      setView(rear_view_pos , rear_view_ang)
   elif direction == "y-":
      setView(front_view_pos , front_view_ang)
   elif direction == "z+":
      setView(top_view_pos , top_view_ang)
   elif direction == "z-":
      setView(bottom_view_pos , bottom_view_ang)
   for i in range(1,50):
      imgDump(True)
      sleep(0.05)

def thespinn(direction):
   #TODO
   #This function is barely begun.
   #Only direction "z-" have been coded and it does not work as intended.
   tmp=0
   if direction == "x+":
      tmp = rear_view_ang[0]
   elif direction == "x-":
      tmp = front_view_ang[0]
   elif direction == "y+":
      tmp = rear_view_ang[0]
   elif direction == "y-":
      tmp = front_view_ang[0]
   elif direction == "z+":
      tmp = top_view_ang[0]
   elif direction == "z-":
      tmp = bottom_view_pos[1]
   for i in range(0,2):
      for j in range(0,40):
         if direction == "x+":
            setView(rear_view_pos , rear_view_ang)
         elif direction == "x-":
            setView(front_view_pos , front_view_ang)
         elif direction == "y+":
            setView(rear_view_pos , rear_view_ang)
         elif direction == "y-":
            setView(front_view_pos , front_view_ang)
         elif direction == "z+":
            setView(top_view_pos , top_view_ang)
         elif direction == "z-":
            tmp -= 2
            setView(App.Vector(bottom_view_pos[0], \
               tmp, bottom_view_pos[2]) , bottom_view_ang)
         sleep(0.05)
         imgDump(True)

def startAnimation():
   # Animation sequence
   animate(ani_1)
   animate(ani_2)
   animate(ani_3)
   animate(ani_4)
#	   animate(ani_5)
#	   animate(ani_6)
#	   animate(ani_7)
#	   animate(ani_8)
#	   animate(ani_9)
#	   animate(ani_10)
#	   animate(ani_11)
   animate(ani_12)
   theend(ani_end)
   thespinn(ani_end)

# + + + + + + + + + + + +              Main             + + + + + + + + + + + +
# Set full screen
# TODO Figure out what can be removed.
# TODO Could be OS dependent.
App.setActiveDocument(doc_name)
App.ActiveDocument=App.getDocument(doc_name)
ActiveDocument=Gui.getDocument(doc_name)
Gui.updateGui()
# ** Contributions from wmayer **
mw=QtGui.qApp.activeWindow()
ev=QtGui.QKeyEvent(QtCore.QEvent.KeyPress,\
   QtCore.Qt.Key_F11,QtCore.Qt.NoModifier)
QtGui.qApp.sendEvent(mw,ev)

# A short break for the GUI to catch up
Gui.updateGui( )
sleep(ani_startwait/1000)

# ** Contributions from wmayer **
# TODO Figure out what can be removed.
v=Gui.ActiveDocument.ActiveView.getViewer()
vp = v.getViewportRegion()
s = vp.getViewportSizePixels().getValue()
sep=coin.SoSeparator()
cam=coin.SoOrthographicCamera()
img=coin.SoImage()
mov=coin.SoTranslation()
root=v.getSceneManager().getSceneGraph()
sep.addChild(cam)
sep.addChild(mov)
sep.addChild(img)

# Adjust ani_offset if Modulus != 0
ani_offset_dist = ani_offset_dist - ani_offset_dist % ani_dist

# Calculate the required number of steps for the animation
ani_step = ani_offset_dist / ani_dist
getBlankScreen()
startAnimation()

#	#Make a MP4 file
#	os.chdir(image_path)
#	# TODO Use variables where appropriate.
#	subprocess.call(['avconv', '-r', '25', '-i', 'e%05d.png', '-c:v', \
#	   'libx264', '-r', '25', '-pix_fmt', 'yuv420p', 'assy.mp4'])
#	sleep(1)
#	#Wake up bell ! ! !
#	# TODO Could be OS dependent.
#	subprocess.call(['beep', '-f', '200', '-l', '500', '-n', '-f', \
#	   '350', '-l', '500', '-n', '-f', '600', '-l', '500'])
