FreeCad Python assembly animation

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
ralvejd
Posts: 58
Joined: Wed Feb 20, 2013 10:31 pm
Location: Sweden

FreeCad Python assembly animation

Post by ralvejd »

Hello!

I have created my first Python script, a assembly animation. :geek: =NOT
As I am new to Python programming there are probably better ways to write this code.

Limitations:
The current version only supports installation in pure X, Y or Z directions.
As the script is written the speed falls with the number of parts shown.
Proposals for performance improvements are appreciated.

See the animation on YouTube.
In the end everything goes unbearably slow.


I guess the frequent use of this function is not optimal?

Code: Select all

Gui.updateGui( )
Attaches the CAD file.
Assy.fcstd
(437.25 KiB) Downloaded 1667 times
The complete script.

Code: Select all

#******************************************************************************
#
# Assembly animation by Joakim Isaksson 20140803
#
#******************************************************************************

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

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

doc_name = "Assy"		# The document name of the FreeCAD model
ani_startwait = 3000	# Milliseconds before the animation starting
ani_partwait	= 50	# Milliseconds before the part begin to move
ani_bitrate = 10		# Milliseconds between part moves
ani_dist = 5			# Distance part moves each time
ani_offset_dist = 200	# 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-" , "ChassieBack_001" , "ChassieWall_001" , "ChassieWall_002" ,
           "ChassieWall_003" , "ChassieWall_004" , "ChassieWall_005" ,
           "ChassieFront_001" ) ;
ani_2  = ( "z-" , "ISO4762_M4x12_001" , "ISO4762_M4x12_002" ,
           "ISO4762_M4x12_003" , "ISO4762_M4x12_004" , "ISO4762_M4x12_005" ,
           "ISO4762_M4x12_006" , "ISO4762_M4x12_007" , "ISO4762_M4x12_008" ,
           "ISO4762_M4x12_009" , "ISO4762_M4x12_010" ) ;
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+" , "ISO10642_M3x8_010" , "ISO10642_M3x8_011" ,
           "ISO10642_M3x8_012" , "ISO10642_M3x8_013" , "ISO10642_M3x8_014" ,
           "ISO10642_M3x8_015" , "ISO10642_M3x8_016" , "ISO10642_M3x8_017" ,
           "ISO10642_M3x8_018" , "ISO10642_M3x8_019" ) ;

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

# 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  + + + + + +

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

def pulse(milliseconds):
	for i in range(milliseconds/10  ):
		Gui.updateGui( )
		sleep(0.01)

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:
			#App.Console.PrintMessage(str(i.Name) + "  <->  " + i.Label + "\n")
			return str(i.Name)

def animate(aniList):
	total = len(aniList)
	for n in range(1,total):
		direction = aniList[0]
		#App.Console.PrintMessage(str(aniList) + " : " + str(direction) + "\n")
		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
			pulse(ani_partwait)
			
			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))
				pulse(ani_bitrate)

		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
			pulse(ani_partwait)
			
			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))
				pulse(ani_bitrate)

		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
			pulse(ani_partwait)
			
			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))
				pulse(ani_bitrate)

		elif direction == "y-":
			setView(front_view_pos , front_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
			pulse(ani_partwait)
			
			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))
				pulse(ani_bitrate)

		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
			pulse(ani_partwait)
			
			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))
				pulse(ani_bitrate)

		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
			pulse(ani_partwait)
			
			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))
				pulse(ani_bitrate)

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)

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)
	Gui.ActiveDocument.ActiveView.startAnimating(0,1,0,0.2) #TODO fixit

# + + + + + + Main + + + + + +
# Set full screen
App.setActiveDocument(doc_name)
App.ActiveDocument=App.getDocument(doc_name)
ActiveDocument=Gui.getDocument(doc_name)
Gui.updateGui ( )
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
pulse(ani_startwait)

# 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

startAnimation()
Feel free to do what you want with the attached file and script.

OS: Debian GNU/Linux 7.6 (wheezy)
Word size: 64-bit
Version: 0.15.3809 (Git)
Branch: master
Hash: 7a3ddd5bae60dfde56d5ca8254cd5b42593cb91e
Python version: 2.7.3
Qt version: 4.8.2
Coin version: 3.1.3
SoQt version: 1.5.0
OCC version: 6.7.0
sorry for my outrageous English, but I have a bad excuse
User avatar
rockn
Veteran
Posts: 1791
Joined: Wed Sep 28, 2011 10:39 am
Location: Toulouse, France
Contact:

Re: FreeCad Python assembly animation

Post by rockn »

Wow, really nice !
Formations - Assistance - Développement : https://freecad-france.com
wmayer
Founder
Posts: 20317
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: FreeCad Python assembly animation

Post by wmayer »

Very nice!
I guess the frequent use of this function is not optimal?
No, it's not. If you call it very frequently the 3d view gets repainted even when there is no need for it.

But just reducing the number of calls of updateGui() or using other techniques like "engines" (see Coin3d docs) doesn't solve this issue because when you show all objects and rotate or pan manually you'll see that it is very slow.

I think the only way of improving this significantly is by cheating heavily. So, what you can try:
* use an SoTexture2 or SoImage node
* once an object has reached its target position make a screenshot of the 3d view

Code: Select all

Gui.ActiveDocument.ActiveView.saveImage(filename,width,height)
* hide the object in the 3d view
* load the new image into the texture/image node
* start animating the next object
* only when you rotate the whole model you must tmp. show all moved objects

Rendering an image happens in almost no time and a frequent call of updateGui() shouldn't have such a negative effect when only rendering an image and a single object
User avatar
ralvejd
Posts: 58
Joined: Wed Feb 20, 2013 10:31 pm
Location: Sweden

Re: FreeCad Python assembly animation

Post by ralvejd »

Thanks for your comments and suggestions.

May be a bit too complicated for a beginner like me.

I'll sleep on it for a few days or weeks. :?
sorry for my outrageous English, but I have a bad excuse
wmayer
Founder
Posts: 20317
Joined: Thu Feb 19, 2009 10:32 am
Contact:

Re: FreeCad Python assembly animation

Post by wmayer »

Attached is a modified version of your script that after a group of objects has been moved a screenshot is created and set as background in the scene.
In some cases you see that objects are painted on top but there is no loss of speed.

Code: Select all

#******************************************************************************
#
# Assembly animation by Joakim Isaksson 20140803
#
#******************************************************************************
#import rpdb2
#rpdb2.start_embedded_debugger("password")

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

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

doc_name = "Assy"      # The document name of the FreeCAD model
ani_startwait = 3000   # Milliseconds before the animation starting
ani_partwait   = 50   # Milliseconds before the part begin to move
ani_bitrate = 10      # Milliseconds between part moves
ani_dist = 5         # Distance part moves each time
ani_offset_dist = 200   # Distance part is moved before the animation starts
ani_image = "C:/Tmp/image.png"

# 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-" , "ChassieBack_001" , "ChassieWall_001" , "ChassieWall_002" ,
           "ChassieWall_003" , "ChassieWall_004" , "ChassieWall_005" ,
           "ChassieFront_001" ) ;
ani_2  = ( "z-" , "ISO4762_M4x12_001" , "ISO4762_M4x12_002" ,
           "ISO4762_M4x12_003" , "ISO4762_M4x12_004" , "ISO4762_M4x12_005" ,
           "ISO4762_M4x12_006" , "ISO4762_M4x12_007" , "ISO4762_M4x12_008" ,
           "ISO4762_M4x12_009" , "ISO4762_M4x12_010" ) ;
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+" , "ISO10642_M3x8_010" , "ISO10642_M3x8_011" ,
           "ISO10642_M3x8_012" , "ISO10642_M3x8_013" , "ISO10642_M3x8_014" ,
           "ISO10642_M3x8_015" , "ISO10642_M3x8_016" , "ISO10642_M3x8_017" ,
           "ISO10642_M3x8_018" , "ISO10642_M3x8_019" ) ;

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

# 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  + + + + + +

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

def pulse(milliseconds):
   for i in range(milliseconds/10  ):
      Gui.updateGui( )
      sleep(0.01)

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:
         #App.Console.PrintMessage(str(i.Name) + "  <->  " + i.Label + "\n")
         return str(i.Name)

def setCamera(aniList):
   direction = aniList[0]
   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)
      

def animate(aniList):
   total = len(aniList)
   for n in range(1,total):
      direction = aniList[0]
      #App.Console.PrintMessage(str(aniList) + " : " + str(direction) + "\n")
      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
         pulse(ani_partwait)
         
         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))
            pulse(ani_bitrate)

      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
         pulse(ani_partwait)
         
         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))
            pulse(ani_bitrate)

      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
         pulse(ani_partwait)
         
         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))
            pulse(ani_bitrate)

      elif direction == "y-":
         #setView(front_view_pos , front_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
         pulse(ani_partwait)
         
         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))
            pulse(ani_bitrate)

      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
         pulse(ani_partwait)
         
         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))
            pulse(ani_bitrate)

      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
         pulse(ani_partwait)
         
         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))
            pulse(ani_bitrate)

def getViewObjects(aniList):
   total = len(aniList)
   views = []
   for n in range(1,total):
      part_name = getPartName(aniList[n])
      views.append(App.getDocument(doc_name).getObject(part_name).ViewObject)
   return views

def hideViewObjects(views):
   for n in views:
      n.hide()

def showViewObjects(views):
   for n in views:
      n.show()

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)


def startAnimation():
   Gui.ActiveDocument.ActiveView.setAnimationEnabled(False)
   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)
   mov.translation.setValue(-float(s[0])/float(s[1]),-1,-9)
   
   views = []
   ani_list = [ani_1, ani_2, ani_3, ani_4, ani_5, ani_6, ani_7, ani_8, ani_9, ani_10, ani_11, ani_12]
   
 # Animation sequence
   for i in ani_list:
      setCamera(i)
      root.removeChild(sep)
      showViewObjects(views)
      Gui.ActiveDocument.ActiveView.saveImage(ani_image,s[0],s[1])
      img.filename.setValue(ani_image)
      hideViewObjects(views)
      root.insertChild(sep,0)
      animate(i)
      views.extend(getViewObjects(i))

   root.removeChild(sep)
   showViewObjects(views)
   theend(ani_end)
   Gui.ActiveDocument.ActiveView.setAnimationEnabled(True)
   Gui.ActiveDocument.ActiveView.startAnimating(0,1,0,0.2) #TODO fixit

# + + + + + + Main + + + + + +
# Set full screen
App.setActiveDocument(doc_name)
App.ActiveDocument=App.getDocument(doc_name)
ActiveDocument=Gui.getDocument(doc_name)
Gui.updateGui ( )
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
pulse(ani_startwait)

# 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

startAnimation()
User avatar
ralvejd
Posts: 58
Joined: Wed Feb 20, 2013 10:31 pm
Location: Sweden

Re: FreeCad Python assembly animation

Post by ralvejd »

Thanks wmayer!

You gave me some ideas and solutions to work with.
Right now I hacks on a script that creates a nice animation through screenshots and "avconv" but takes a long time to render.
This deviates from the original objective to make an animation in real time. :?
I'll be back with the script and YT demo asap.
sorry for my outrageous English, but I have a bad excuse
mario52
Veteran
Posts: 4696
Joined: Wed May 16, 2012 2:13 pm

Re: FreeCad Python assembly animation

Post by mario52 »

hi
ralvejd wrote:Right now I hacks on a script that creates a nice animation through screenshots
I used this method to create an animation from a DXF file using the development of a piece (pince) with 10 layers in this it was GWBASIC on PCxT 8088
mario
Maybe you need a special feature, go into Macros_recipes and Code_snippets, Topological_data_scripting.
My macros on Gist.github here complete macros Wiki and forum.
User avatar
ralvejd
Posts: 58
Joined: Wed Feb 20, 2013 10:31 pm
Location: Sweden

Re: FreeCad Python assembly animation

Post by ralvejd »

mario52 wrote: it was GWBASIC on PCxT 8088
:o
sorry for my outrageous English, but I have a bad excuse
User avatar
ralvejd
Posts: 58
Joined: Wed Feb 20, 2013 10:31 pm
Location: Sweden

Re: FreeCad Python assembly animation

Post by ralvejd »

Hello there!

I now have a script ready but it needs to be polished.
The script assumes that the user has updated the configuration to local conditions. ;)
You have been warned!

A demo is up on YouTube.
It got 2:18 minutes long and took about an hour to generate.

The script:

Code: Select all

#******************************************************************************
#
# Assembly animation by Joakim Isaksson 20140808
#
#******************************************************************************
#
# USAGE:
#  Open your CAD assembly file and zoom and orient the model as desired.
#  Hide all 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. killall 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 = "Assy"			# The document name of the FreeCAD model
image_path = "/tmp/assy"	# Set path to your requirements.
ani_startwait = 3000		# Milliseconds before the animation starting
ani_partwait	= 50		# Milliseconds before the part begin to move
ani_bitrate = 10			# Milliseconds between part moves
ani_dist = 5				# Distance part moves each time
ani_offset_dist = 200		# 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-" , "ChassieBack_001" , "ChassieWall_001" , "ChassieWall_002" ,
           "ChassieWall_003" , "ChassieWall_004" , "ChassieWall_005" ,
           "ChassieFront_001" ) ;
ani_2  = ( "z-" , "ISO4762_M4x12_001" , "ISO4762_M4x12_002" ,
           "ISO4762_M4x12_003" , "ISO4762_M4x12_004" , "ISO4762_M4x12_005" ,
           "ISO4762_M4x12_006" , "ISO4762_M4x12_007" , "ISO4762_M4x12_008" ,
           "ISO4762_M4x12_009" , "ISO4762_M4x12_010" ) ;
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+" , "ISO10642_M3x8_010" , "ISO10642_M3x8_011" ,
           "ISO10642_M3x8_012" , "ISO10642_M3x8_013" , "ISO10642_M3x8_014" ,
           "ISO10642_M3x8_015" , "ISO10642_M3x8_016" , "ISO10642_M3x8_017" ,
           "ISO10642_M3x8_018" , "ISO10642_M3x8_019" ) ;

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

# 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.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
			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
			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
			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)
			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
			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
			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
			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'])
Feel free to do what you want with the script.
Last edited by ralvejd on Sat Apr 09, 2016 8:51 pm, edited 1 time in total.
sorry for my outrageous English, but I have a bad excuse
Jim_Jim
Posts: 7
Joined: Wed Dec 02, 2015 12:52 am

Re: FreeCad Python assembly animation

Post by Jim_Jim »

Temporary improvement (Windows only here, but I'm sure something like this is possible under all Systems) ...

-----------

Summary:
- real screenshots instead of saveImage()
- ffmpeg instead of avconv
- no visible active selection while recording

-----------

Before I start ... all this works "out of the box" with FreeCAD 0.14, but maybe not on 0.15 and 0.16 as even the original script just crashs - at least with my scene. I found that taking an ordinary screenshot from the FreeCAD menu (Tools/Save Screen) leads to a good image in 0.14 but a 0 byte "image" in 0.15 and 0.16. I guess that there is some connection, but am not sure. Maybe it's just my scene and my installation on an old WinXP notebook ... YMMV, just make a test ;-) ...

I used the script in ralvejd's last post to this thread (see above) ...

Just in case somebody else attempts to use that script and is not satisfied because there are differences between the images rendered in the 3D view and the pictures saved to disk by saveImage() ... In my case these differences were so serious that I had to find a solution. As long as I use image planes I get wrong 3D object clipping in the pictures saved to disk while everything looks great in the FreeCAD 3D render image.

I first tried to solve the problem inside FreeCAD but with no success. I have reported the bugs and maybe at some point they are fixed. Until then I found it better to avoid saveImage() and to do real Windows screenshots instead. For that purpose I downloaded NirCmd ...

http://www.nirsoft.net/utils/nircmd.zip

That tool has many uses but here we just call it to take screenshots and save them to disk ... Here's what I changed in the script (three lines in the middle) ...

Code: Select all

   ani_image = image_path + "/" + image_prefix + image_id + image_sufix
   if update_gui == True:
      Gui.updateGui( )
      sleep(0.05)

#   Gui.ActiveDocument.ActiveView.saveImage(ani_image,s[0],s[1])

   subprocess.call(["F:\\temp\\freecad_animation\\nircmd.exe", 'savescreenshot', ani_image])
#   sleep(1)

   img.filename.setValue(ani_image)
   sleep(0.05)
Short ... comment out saveImage() and add a call to NirCmd.

You see how I call NirCmd with its absolute path in my system. I have already so many things in my PATH that I avoid to add more to it, but maybe I'll just copy it into a system folder that is already included. Just make sure to replace "F:\\temp\\freecad_animation\\nircmd.exe" with something that works in your system.

I'm not sure about the sleep() ... my scene renders OK on my notebook and it is my impression that FreeCAD waits of the screenshots. But if you have a complex scene and note that the images are somehow incomplete just enable that call to sleep(1) and see if that changes anything.

-----------

Another thing ... avconv ... in a hurry I wasn't able to get that running on the Windows machine (seems like I didn't install MinGW on this one yet :-) ...) ... So for now I use FFmpeg ...

http://ffmpeg.zeranoe.com/builds/win32/ ... -static.7z

Didn't integrate it into the script yet (shouldn't be difficult but I prefer not to render every time when I just look for the best angles) ... so I commented out the call to avconv and instead I use the Windows command line for a call like this ...

Code: Select all

E:\...\ffmpeg-20151130-git-7b11eea-win32-static\bin>
ffmpeg -framerate 25  -start_number 25 -i F:\temp\freecad_animation\output\e%05d.png -i House-synth-loop_4.wav -c:v libx264 -r 25 -pix_fmt yuv420p out.mp4
As you can see I again call the tool from its own directory (now called "folder" ^^) to avoid PATH-ing it and I give the full path to the images. You would have to change that so it fits your system. In very nasty fashion (and for the same reason) I put the music file directly into the directory of the tool and that's also where the resulting MP4 is generated. I would have used absolute paths for those too but then this command line just gets too long. So - sorry for the bad style and make it better :-) !

Note: Here I start to use the images from number 25 onwards. That's because the first 25 images are not usable as the "fullscreen" FreeCAD image is actually sitting in the upper left corner. I guess that's a bug in the script. I didn't investigate. You can read about the other call parameters if you do an online search for "FFmpeg PNG MP4". I found the following helpful enough ...

https://trac.ffmpeg.org/wiki/Create%20a ... m%20images

... it also explains about how to add music ;-) ...

-----------

Anything else ? Well ... yes ... I didn't like the selection color applied to the object currently moving. At first I disabled the selection color in the preferences but that got annoying over time so I modified the animate() function of the script. I added a global deselection in all the [x+, x-, .. z-]
branches before the call of imgDump... Here's an example ...

Code: Select all

      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)


... I'm sure this can be done more elegantly - actually the entire script can be improved in many places. But I'm totally new to FreeCAD and have never used Python ... so I just did what worked, sorry.

I wish to thank ralvejd for this great script. I would not have been able to get my video done without it. Looks really gread and I think something like this needs to become a part of FreeCAD 3.0 ;-) ... I found it very useful, THANKS !
Last edited by Jim_Jim on Sat Dec 05, 2015 4:02 pm, edited 1 time in total.
Post Reply