Animated 3D models of Steam Engines

Show off your FreeCAD projects here!
Posts: 33
Joined: Sun Nov 02, 2014 4:21 pm

Animated 3D models of Steam Engines

Postby romartin » Thu Dec 18, 2014 5:28 pm



As a learning exercise in FreeCAD modelling I decided to remodel the design for a pair of vertical steam engine models. This design, done during 2011 using a well known non-parametric commercial CAD tool, was based on a single cylinder model my twin brother and I designed (with no design tools!) and, under paternal supervision in our home workshop, built in 1956 when we were 14 years old.

The design allows two configurations: one with a single cylinder and one with two cylinders. Almost all part types are common; only three part types depend on the configuration (base, crank shaft, cylinder platform) and of course the single cylinder configuration doesn't use the inlet and exhaust manifolds. The design does not forsee the usual mechanism with a lever and twin eccentrics to invert the direction of rotation of the crank shaft. Even without this option, I felt that I'd probably bitten off more than I could che[*]w! The photo shows the dual cylinder version of the engine which I built during 2012.
Photo of the completed model built in my home workshop.
FrontRight.jpg (52.08 KiB) Viewed 10806 times
Two important objectives for this remodelling project were:
  • add a reversing mechanism option
  • structure the models so that the two key FreeCAD novelties (for me), namely the parametric capability and the Phython macro capability, could be used to provide animations.
The screen shot below shows a view of the Overall Assembly of the dual cylinder configuration.
Screen snap of the 3D window showing the dual cylinder version with Reversing option
VerticalDualModelView.jpg (56.45 KiB) Viewed 10806 times
There are two aspects of the modelling approach I took which may be of interest to some readers although I'm sure its all been done before. The first relates to handling the complexity of the models, the second relates to the animation of the somewhat complex movements of the reversing link mechanism.


SubParts and Parts
The design with the reversing link option comprises over seventy distinct types of part and subpart not counting the nots, bolts and screws. By subpart I mean an item which exists briefly during the fabrication process but which, together with other subparts, form a single type of part by getting joined by an irreversable process such as soldering, welding, glueing or rivetting. So one aspect of complexity derives simply from the large number of distinct shapes which must be modelled and which (not yet done) must appear on readable design documents showing the appropriate projections, dimensions and instructions to favour smooth and reliable fabrication in the home workshop.

As a start to handling this aspect of complexity I divided the parts and subparts into groups of related parts and modelled each such group in a separate FreeCAD file. Within each file I used the FreeCAD Group facility to separate Subparts and Parts. Within each of these Groups, the members are allocated placements in which the Y coordinate is always 0 but the X and Z coordinates increase by an appropriate amount to ensure that the six standard views (top, front, back ..) never show overlap between adjacent members even when all members of the group are Visible.

A second aspect of complexity relates to modeling sub-assemblies of Parts and the overall assemblies of sub-assemblies. Like all other FreeCAD fans, I cant wait to use the coming Assembly Tool; in the meantime I decided to be pragmatic, ie to build up my sub-assemblies by using Simple Copies of the original parametric designs of the parts. So to get a part to participate in a sub-assembly I open the file containing the Part, make a simple copy thereof in the Part file, Copy the simple copy to the clip-board, close the Part File without saving, and finally paste the simple copy into the sub-assembly file and adjust its Placement parameters to get the desired fit into the sub-assembly.

The non-moving parts are divided into three sub-assemblies held in distinct files the Base Assy, the Vertical Structure Assy, and the Cylinder Assy. Within each of these files there are two versions in distinct FreeCAD Groups - a mono version and a dual version.

The sub-assemblies for the moving parts were divided between two FreeCAD files - one for the configurations without the reversing option and one for the Reversing Option sub-assemblies. Each sub-assembly is held in a distinct FreeCAD Group. There is one such sub-assembly for each group of parts which are rigidly held together and which move as a whole.

Examples are the Dual Crank Shaft Assy, the Eccentric Strap Assy, The ConRod Assy etc. The Parts in the Dual Crank Shaft Assy are one Dual Crank Shaft, one Flywheel, one Flywheel grubscrew, two Eccentrics and two Eccentric grubscrews. To facilitate the animation of a single moving assembly, it is important to place the reference point of the subassembly at the coordinate origin and to orient the individual parts correctly. Then the final step for modelling a moving sub-assembly is to unify all the parts as a FreeCAD Compound with its reference point at the origin and with a rotational angle of zero. This precaution allows the animation of the entire sub-assembly to be controlled by setting the Placement parameters of the Compound.

Overall Assemblies
The two overall assemblies presented here are held in distinct FreeCAD groups in a single file. The choice of the engine to animate is made by enabling the visibility of one of the two. The file also contains a copy of the conceptual sketch of the reversing mechanism which, as explained above, is used by the animation macro.


During an animation of the motion of an engine, the independent variable is of course the rotational angle of the crank shaft about the X axis. The animation simply steps this angle cyclically by a suitable small increment, and must compute and set the consequent Placements for all the moving sub-assemblies. Animation of the Crank Shaft Assy, the ConRod Assy and the Piston Assy is relatively easy because the positions of the reference points and the rotational angles can be readily calculated using simple and well-known trig formulae. But calculating the placements of the sub-assemblies of the reversing mechanism is significantly more complex and proved to be beyond my limited math!

As a first step in designing this mechanism I had used the FreeCAD Sketcher to construct a conceptual model of the mechanism and found to my joy that Sketcher solved in a jiffy the math I had found too tough, and also that I could also animate the Sketch via a very simple macro which cyclically steps the value of the Constraint which models the rotational angle of the eccentics. So I had the idea of using this Sketch in the3D animation macro to extract the appropriate placements for the moving sub-assemblies of the 3D model. I tried this out and it worked fine.

The Reversing Mechanism Sketch
Below is a screen shot of this conceptual Sketch; with an image editor I have added identifying letters for the line ends. These identifiers are used in the 3D animation macro as part of the names of the variables used to hold the coordinates of the points extracted from the sketch.
View of the conceptual sketch of the Reversing Mechanism with identifying letters at the nodes
ReversingSketchSnap.jpg (20.26 KiB) Viewed 10806 times
Here is the short macro which animates this sketch.

Code: Select all

#Macro Begin: AnimateReversingLinkSketch
import FreeCAD
import time
sketch = App.ActiveDocument.Sketch
for rev in range(0,10):
	for theta in range(90,451,10):
		sketch.setDatum(39,App.Units.Quantity(str(theta) + ' deg'))
#Macro End:	
To make better use of screen space, the sketch is laid out horizontally so, when using Sketch coordinates to position 3D model sub-assemblies I have to map the Sketch's X coordinate to the 3D model's -Z coordinate. In this sketch:
  • OA and OB represent the offset axes of the two eccentrics. AOB always forms a straight line.
  • AC and BD represent the two eccentric straps.
  • EF represents the center line of the link's slot while C, D and G represent the centers of the three holes in this link about which the eccentric straps (AC and BD) and the reversing link strap GH can freely swivel.
  • KL represents the valve assembly; it is constrained to lie on the sketches X axis and its end L is constrained to lie on the line EF.
  • HI and IJ represent respectively the reversing arm and the reversing lever; the angle between these is constrained to be 90 deg. To run forewards (backwards) the angle between HI and the horizontal must be +14 (-14) deg. The sketch is shown in the position for going in reverse; in this position the horizontal movement of the valve is in effect determined by the eccentric OA.
  • The lengths of all lines are fixed by constraints
The Animation Macro
Here is a single macro of about 300 lines which animates both the mono and the dual cylinder configurations. To use it the Report View and the Python Console must be visible and both Python errors and Python internal output should be redirected to the Report View.

Code: Select all

# Macro Begin: AnimateReversingEngines
import FreeCAD
import FreeCADGui
from PySide import QtCore
from math import sqrt, pi, sin, cos, asin,atan
import string

def trace(s):
	"""Trace routine for debugging"""
	#print s

def askWhatToDo():
	"""Asks the operator what to do next and sets up the action to obey"""
	global mono, timer, timerIsConnected, StopAnimation, state, direction, periodMilliseconds
	global theta, step, stepsPerRev, armAngle0Deg, armAngleDeg, Z0
	if timerIsConnected:
		timer.timeout.disconnect()	# Stop timeout events
		timerIsConnected = False
	if direction != "F" and direction != "B":
		print "Hello!"
		reply = "D"
		reply = "?"
	# Loop acquires a valid reply to the question
	while True:
		if reply == "?":
			print "On Python console, please enter D (Direction change), R (Run) or Q (Quit), followed by Enter"
			reply = raw_input();
		ok = (len(reply) == 1)
		if ok:
			reply = string.upper(reply[0])
			if reply == 'Q':
				print "Bye\n"
			# Check that exactly one OverallAsy is visible
			mono = FreeCADGui.ActiveDocument.getObject("Group006").Visibility
			dual = FreeCADGui.ActiveDocument.getObject("Group003").Visibility
			if (mono and dual) or not(mono or dual):
				print "Either Group - Overall1Assy or Group - Overall2Assy, but not both, must be visible!"
				ok = True
			elif reply == 'D' or reply == 'R':
		if not ok:
			print "Bad reply! Try again please."
		reply = "?"
	# Reply is D or R
	if mono:
	print "Done Engine setup"
	if reply == "D":
		if direction == "F":
			direction = "B"
			direction = "F"
		# Acquire current angle in Sketch of EccsArm
		sketch = skDoc.Sketch
		skLinkArmHtoI =sketch.Geometry[10]
		Hy = skLinkArmHtoI.StartPoint.y
		Hz = Z0 - skLinkArmHtoI.StartPoint.x
		Iy = skLinkArmHtoI.EndPoint.y
		Iz = Z0 - skLinkArmHtoI.EndPoint.x
		armAngle0Deg = -atan((Hy-Iy)/(Hz-Iz)) / fac
		if direction == 'F':
			armAngleDeg = armAngleFDeg
			armAngleDeg = armAngleBDeg
		# Start animation of changing direction
		print "Starting animation of direction change"
		timerIsConnected = True
		step = 1
		# Start animation of running
		print "Starting animation of running the engine. Press ENTER to stop animation at end of current rotation."
		StopAnimation = False
		timerIsConnected = True
		theta = 0
		step = stepsPerRev
		reply = raw_input()
		StopAnimation = True

def PistonAnimationStep(theta, X, ConRodAssy, PistonAssy):
	""" Performs one animation step for a ConRodAssy and a PistonAssy """
	global fac, CrankThrow, ConRodLength, Z0
	thetaRad = theta * fac
	YY = -CrankThrow * sin(thetaRad)
	ZZ = CrankThrow * cos(thetaRad) + Z0
	psiRad = asin(YY/ConRodLength)
	psiDeg = psiRad / fac
	ConRodAssy.Placement = App.Placement(App.Vector(X,YY,ZZ), App.Rotation(App.Vector(1,0,0), psiDeg))
	ZZ = ZZ + ConRodLength * cos(psiRad)
	PistonAssy.Placement = App.Placement(App.Vector(X,0,ZZ), App.Rotation(App.Vector(0,0,1), 0))

def ReversingAssyAnimationStep(theta, alpha, X, Eccs, ERodA, ERodB, ELink, ESlipper, EStrap, EValve, ELever):
	""" Performs one animation step for the moving parts of a reversing link assembly"""
	global skDoc, Z0
	# Set angle for eccentics and for reversing lever in Sketch and recalculate sketch geometry
	sketch = skDoc.Sketch
	sketch.setDatum(39, App.Units.Quantity(str(theta + 90) + ' deg'))
	sketch.setDatum(37, App.Units.Quantity(str(alpha) + ' deg'))
	# Extract GeomLineSegment objects from Sketch
	skCrankOtoB = sketch.Geometry[0]
	skCrankOtoA = sketch.Geometry[1]
	skRodAtoC = sketch.Geometry[2]
	skRodBtoD = sketch.Geometry[3]
	skLinkLobeCtoE = sketch.Geometry[5]
	skLinkLobeDtoF = sketch.Geometry[6]
	skLinkEtoF = sketch.Geometry[7]
	skLinkFtoG = sketch.Geometry[8]
	skLinkStrapGtoH = sketch.Geometry[9]
	skLinkArmHtoI =sketch.Geometry[10]
	skValveRodKtoL = sketch.Geometry[14]
	skControlLeverItoJ = sketch.Geometry[15]
	# Extract point coordinates from Sketch lines and transform from sketch coordinates to model coordinates
	# Transformation is as follows: Ym = Ys;  Zm = Z0 - Xs
	Ay = skCrankOtoA.EndPoint.y
	Az = Z0 - skCrankOtoA.EndPoint.x
	trace("Ay=" + str(Ay) + ", Az=" + str(Az) + "\n")
	By = skCrankOtoB.EndPoint.y
	Bz = Z0 - skCrankOtoB.EndPoint.x
	trace("By=" + str(By) + ", Bz=" + str(Bz) + "\n")
	Cy = skRodAtoC.EndPoint.y
	Cz = Z0 - skRodAtoC.EndPoint.x
	trace("Cy=" + str(Cy) + ", Cz=" + str(Cz) + "\n")
	Dy = skRodBtoD.EndPoint.y
	Dz = Z0 - skRodBtoD.EndPoint.x
	trace("Dy=" + str(Dy) + ", Dz=" + str(Dz) + "\n")
	Ey = skLinkLobeCtoE.EndPoint.y
	Ez = Z0 - skLinkLobeCtoE.EndPoint.x
	trace("Ey=" + str(Ey) + ", Ez=" + str(Ez) + "\n")
	Gy = skLinkFtoG.EndPoint.y
	Gz = Z0 - skLinkFtoG.EndPoint.x
	trace("Gy=" + str(Gy) + ", Gz=" + str(Gz) + "\n")
	Hy = skLinkStrapGtoH.EndPoint.y
	Hz = Z0 - skLinkStrapGtoH.EndPoint.x
	trace("Hy=" + str(Hy) + ", Hz=" + str(Hz) + "\n")
	Iy = skLinkArmHtoI.EndPoint.y
	Iz = Z0 - skLinkArmHtoI.EndPoint.x
	trace("Iy=" + str(Iy) + ", Iz=" + str(Iz) + "\n")
	Jy = skControlLeverItoJ.EndPoint.y
	Jz = Z0 - skControlLeverItoJ.EndPoint.x
	trace("Jy=" + str(Jy) + ", Jz=" + str(Jz) + "\n")
	Ly = skValveRodKtoL.EndPoint.y
	Lz = Z0 - skValveRodKtoL.EndPoint.x
	trace("Ly=" + str(Ly) + ", Lz=" + str(Lz) + "\n")
	Eccs.Placement = App.Placement(App.Vector(X,0,Z0), App.Rotation(App.Vector(1,0,0), theta))
	psiDeg = atan((Cy-Ay)/(Cz-Az)) / fac
	ERodA.Placement = App.Placement(App.Vector(X,Ay,Az),App.Rotation(App.Vector(1,0,0),-psiDeg))
	psiDeg = atan((Dy-By)/(Dz-Bz)) / fac
	ERodB.Placement = App.Placement(App.Vector(X,By,Bz),App.Rotation(App.Vector(1,0,0),-psiDeg))
	psiDeg = atan((Cz-Dz)/(Cy-Dy)) / fac
	ELink.Placement = App.Placement(App.Vector(X,Dy,Dz),App.Rotation(App.Vector(1,0,0),psiDeg))
	ESlipper.Placement = App.Placement(App.Vector(X,Ly,Lz),App.Rotation(App.Vector(1,0,0),psiDeg))
	psiDeg = atan((Hz-Gz)/(Hy-Gy)) / fac
	EStrap.Placement = App.Placement(App.Vector(X,Gy,Gz),App.Rotation(App.Vector(1,0,0),psiDeg))
	psiDeg = atan((Hy-Iy)/(Hz-Iz)) / fac
	ELever.Placement = App.Placement(App.Vector(0,Iy,Iz),App.Rotation(App.Vector(1,0,0),-psiDeg))
	EValve.Placement = App.Placement(App.Vector(X,Ly,Lz), App.Rotation(App.Vector(0,0,1), 0))

def animationStep(mono):
	"""Modifies Placements of all movable objects of model using data extracted from Sketch"""
	global theta, armAngle0Deg, Z0, EccsXR,  pistonXR
	global shCrankAssy, shEccsLeverAssy
	global shConRodAssyR, shPistonAssyR, shEccentricsR, shEccRodAssyRA, shEccRodAssyRB
	global shEccsLinkR, shEccsLinkStrapAssyR, shEccsLinkSlipperR, shEccsValveAssyR
	global shConRodAssyL, shPistonAssyL, shEccentricsL, shEccRodAssyLA, shEccRodAssyLB
	global shEccsLinkL, shEccsLinkStrapAssyL, shEccsLinkSlipperL, shEccsValveAssyL
	# Modify placements of 3D moving objects
	shCrankAssy.Placement = App.Placement(App.Vector(0,0,Z0), App.Rotation(App.Vector(1,0,0), theta+90))
	if mono:
		PistonAnimationStep(theta, pistonXR, shConRodAssyR, shPistonAssyR)
		ReversingAssyAnimationStep(theta, armAngle0Deg, EccsXR, shEccentricsR, shEccRodAssyRA, shEccRodAssyRB,\
			 shEccsLinkR, shEccsLinkSlipperR, shEccsLinkStrapAssyR, shEccsValveAssyR, shEccsLeverAssy)
		PistonAnimationStep(theta+90, pistonXR, shConRodAssyR, shPistonAssyR)
		PistonAnimationStep(theta, -pistonXR, shConRodAssyL, shPistonAssyL)
		ReversingAssyAnimationStep(theta+90, armAngle0Deg, EccsXR, shEccentricsR, shEccRodAssyRA, shEccRodAssyRB,\
			 shEccsLinkR, shEccsLinkSlipperR, shEccsLinkStrapAssyR, shEccsValveAssyR, shEccsLeverAssy)
		ReversingAssyAnimationStep(theta, armAngle0Deg, -EccsXR, shEccentricsL, shEccRodAssyLA, shEccRodAssyLB,\
			 shEccsLinkL, shEccsLinkSlipperL, shEccsLinkStrapAssyL, shEccsValveAssyL, shEccsLeverAssy)
	# Update display

def setDirection():
	"""Called by timer. Controls the animation of setting the link according to the required direction"""
	global mono, armAngle0Deg, armAngleDeg, step
	if armAngleDeg != armAngle0Deg:
		# Compute new angle for this step
		if armAngle0Deg < armAngleDeg:
			armAngle0Deg = armAngle0Deg + step
			if armAngle0Deg > armAngleDeg:
				armAngle0Deg = armAngleDeg
			armAngle0Deg = armAngle0Deg - step
			if armAngle0Deg < armAngleDeg:
				armAngle0Deg = armAngleDeg
		# Do the animation step
		# Final position has been reached, so stop this and ask what to do next

def runEngine():
	"""Called by timer. Controls the animation of running the engine"""
	global mono, StopAnimation, theta,  direction, stepsPerRev, stepSizeDeg, step
	step = step - 1	
	if step < 0:			# Check for end of turn
		theta = 0
		step = stepsPerRev
		if StopAnimation:
	if direction == 'F':
		theta = theta + stepSizeDeg
		theta = theta - stepSizeDeg

def SetupMonoEngine():
	# Define geometry constants
	global EccsXR, PillarXR, pistonXR
	pistonXR = 0				# X corrdinate of piston axis for mono cylinder engine
	EccsXR = 26					# X coodinate of eccentric assembly for mono cylinder engine
	PillarXR = 12				# X coordinate of right pillar for mono cylinder engine. Left pillar is at -PillarZR
	# Define variables for 3D shapes of mono cylinder model to animate or position
	global shCrankAssy, shConRodAssyR, shPistonAssyR, shEccentricsR, shEccRodAssyRA, shEccRodAssyRB
	global shEccsLinkR, shEccsLinkStrapAssyR, shEccsLeverAssy, shEccsLinkSlipperR, shEccsValveAssyR
	global shCrabR, shCrabL
	shCrankAssy = assyDoc.getObject("Compound010")
	shConRodAssyR = assyDoc.getObject("Compound011")
	shPistonAssyR = assyDoc.getObject("Compound012")
	shEccentricsR = assyDoc.getObject("Cut001003001")
	shEccRodAssyRA = assyDoc.getObject("Compound")
	shEccRodAssyRB = assyDoc.getObject("Compound001")
	shEccsLinkR = assyDoc.getObject("Cut001005001")
	shEccsLinkStrapAssyR = assyDoc.getObject("Compound002")
	shEccsLeverAssy = assyDoc.getObject("Compound042")
	shEccsLinkSlipperR = assyDoc.getObject("Cut001005002001")
	shEccsValveAssyR = assyDoc.getObject("Compound003")

def SetupDualEngine():
	# Define geometry constants
	global EccsXR, PillarXR, pistonXR
	pistonXR = 16.5				# X corrdinate of piston axis for dual cylinder engine
	EccsXR = 42.5				# X coodinate of eccentric assembly for dual cylinder engine
	PillarXR = 28.5				# X coordinate of right pillar for dual cylinder engine. Left pillar is at -PillarZR
	# Define variables for 3D shapes of dual cylinder model to animate or position
	global shCrankAssy, shEccsLeverAssy
	global shConRodAssyR, shPistonAssyR, shEccentricsR, shEccRodAssyRA, shEccRodAssyRB
	global shEccsLinkR, shEccsLinkStrapAssyR, shEccsLinkSlipperR, shEccsValveAssyR
	global shConRodAssyL, shPistonAssyL, shEccentricsL, shEccRodAssyLA, shEccRodAssyLB
	global shEccsLinkL, shEccsLinkStrapAssyL, shEccsLinkSlipperL, shEccsValveAssyL
	shCrankAssy = assyDoc.getObject("Fusion033003002024")
	shEccsLeverAssy = assyDoc.getObject("Compound055")
	shConRodAssyR = assyDoc.getObject("Compound049")
	shPistonAssyR = assyDoc.getObject("Compound051")
	shEccentricsR = assyDoc.getObject("Cut001005012034004002003001002003003001027")
	shEccRodAssyRA = assyDoc.getObject("Compound043")
	shEccRodAssyRB = assyDoc.getObject("Compound044")
	shEccsLinkR = assyDoc.getObject("Cut001005012034004002003001002003003001025")
	shEccsLinkStrapAssyR = assyDoc.getObject("Compound016")
	shEccsLinkSlipperR = assyDoc.getObject("Cut001005012034004002003001002003003001026")
	shEccsValveAssyR = assyDoc.getObject("Compound045")
	shConRodAssyL = assyDoc.getObject("Compound050")
	shPistonAssyL = assyDoc.getObject("Compound052")
	shEccentricsL = assyDoc.getObject("Cut001005012034004002003001002003003001047")
	shEccRodAssyLA = assyDoc.getObject("Compound046")
	shEccRodAssyLB = assyDoc.getObject("Compound047")
	shEccsLinkL = assyDoc.getObject("Cut001005012034004002003001002003003001048")
	shEccsLinkStrapAssyL = assyDoc.getObject("Compound020")
	shEccsLinkSlipperL = assyDoc.getObject("Cut001005012034004002003001002003003001049")
	shEccsValveAssyL = assyDoc.getObject("Compound048")

assyDoc = App.ActiveDocument
skDoc = App.ActiveDocument

# Define constants
fac = pi / 180				# For converting between radians and degrees
# Define geometry constants
CrankThrow = 13				# Distance between aces of crank shaft and crank pin
ConRodLength = 44			# Distance between axes of crank pin and snall end pin
Z0 = 38						# Z coordinate of crank shaft
armAngleFDeg = -14			# Angle of EccsArm from vertical for running forewards
armAngleBDeg = 14			# Angle of EccsArm from vertical for running backwards
# Define simulation control constants
periodMilliseconds = 30		# Period between animation steps
stepsPerRev = 36			# Number of steps per full rotation. MUST BE A DIVISOR OF 360!!
stepSizeDeg = 360 / stepsPerRev
# Create timer
timer = QtCore.QTimer()
theta = 0
# Ask operator for instructions
direction = '?'
timerIsConnected = False
# Macro End: AnimateReversingEngines
The macro outputs prompts to the Report View and accepts three simple commands via keyboard input from the Python Console. The commands are single letters followed by Enter:
  • D: Changes the direction of the engine by animating the movement of the Reversing lever.
  • R: Runs the engine until the user presses Enter
  • Q: Terminates execution of the macro.
During the development of these models I encountered only one recurring problem. Under certain conditions which I have not managed to characterize, Compounds created with the Part WB lose their ability to retain the different colors assigned to their component shapes. It happens if a Compound is Copied and then Pasted. If you then delete the pasted Compound and create a new one from the same components the new one shows the correct colours again. But it can also happen spontaneously as can be seen in the overall assembly of the dual engine in which all Compounds representing the moving assemblies have lost their colors and are shown in default grey. I have deleted and replaced all these Compounds but the new ones show the true colors for a while and then revert to default grey.

While preparing this post I hit the problem that trying to save the 3D window when a Sketch is in editing mode doesn't work because the size of the text describing the constraints gets grossly enlarged.

A photo of the finished dual engine (56KB)
Screen shot of FreeCAD model (60KB)
Screen shot of the Sketch (24KB)
The Sketch file (8KB)
The Sketch Animation macro (1KB)
The Overall Assemblies file (3.6MB). This file is too large for uploading to the forum so I have made it available on Dropbox via the following link: ... FCStd?dl=0
The Animation Macro (13KB)

OS: Windows
Word size: 64-bit
Version: 0.14.3700 (Git)
Branch: releases/FreeCAD-0-14
Hash: 32f5aae0a64333ec8d5d160dbc46e690510c8fe1
Python version: 2.7.6
Qt version: 4.8.5
Coin version: 4.0.0a
SoQt version: 1.6.0a
The FreeCAD file with the conceptual Sketch
(4.53 KiB) Downloaded 222 times
Posts: 479
Joined: Sun May 11, 2014 7:47 pm
Location: Mexico

Re: Animated 3D models of Steam Engines

Postby drei » Thu Dec 18, 2014 10:11 pm

Allow me to be the first to say that is quite an amazing project.
Need help? Feel free to ask, but please read the guidelines first
Site Admin
Posts: 17268
Joined: Thu Feb 19, 2009 10:32 am

Re: Animated 3D models of Steam Engines

Postby wmayer » Fri Dec 19, 2014 9:24 am

Allow me to be the second to say that is quite an amazing project.
User avatar
Posts: 4691
Joined: Tue Nov 12, 2013 4:06 pm

Re: Animated 3D models of Steam Engines

Postby microelly2 » Fri Dec 19, 2014 10:16 am

Very nice animation

I had the problem to access the inner data of the sketch

This is the solution and a big step forward
# Extract GeomLineSegment objects from Sketch
skCrankOtoB = sketch.Geometry[0]
I will change my Plugger object with this detail
Thank you.
Posts: 33
Joined: Sun Nov 02, 2014 4:21 pm

Re: Animated 3D models of Steam Engines

Postby romartin » Fri Dec 19, 2014 10:22 am

Thank you drei, wmayer and microelly2!
My next FreeCAD learning task is to produce the 2D drawings of the 20 or so parts of the Reversing Mechanism so that I can go and fabricate them in my little home workshop. Some initial skirmishes with the Draft WB lead me to fear that this will not be straight forward, and I'm sure I will need help from the Forum.
User avatar
Posts: 4691
Joined: Tue Nov 12, 2013 4:06 pm

Re: Animated 3D models of Steam Engines

Postby microelly2 » Fri Dec 19, 2014 8:32 pm

Influenced from this model I have extended and improved the functionality of my plugger object.

The example video demonstrates the new functionalities

The animation is controled by a sketch. The adjustor object changes the value of a angle constraint inside the sketch which results in a rotation of the Startpoint of a line of the sketch. The Endpoint of this line is restricted by constraints to the origin. The Startpoint of the other line is restricted to the x-axis . And the Endpoint is connected to the Startpoint of the rotating line.

Some cubes are pluged to the sketch to demonstrate the functionality.
The red cube follows the rotating Point. The blue cube follows the Point of the other line parallel to the x-axis with some offset.

Points in Sketches can change only their x and y coordinates. To allow movements in space there is now the possibility to use a matrix for mapping the xy of a sketch point to the Placement.Base of a part. This is done explicitly for the yellow cube. I think in most cases a simple remapping of the axes will be enough. So there are two simplifications: yz-mode means the mapping x -> y and y -> z, and xz-mode means x -> x and y -> z.

For other cases the user needs some knowledge about linear transformations and has to fill the matrix by hand.

It is always possible to add on the end an extra offset vector.

The realtime animation runs with tranquillizer set to 0.1 sec to see the single steps.

My next step will be to allow access to the other sketcher components (arcs, lenght, angles) from the Plugger. Actualy there are Sketch.Object.EndPoint and Sketch.Object.StartPoint to a line object.

This forum is a wounderful source of new ideas.

The Link to my testcase: ... test.fcstd
It works with the actual animation workbench in my repo ... 7dafb01e1d
User avatar
Posts: 6075
Joined: Thu Jul 11, 2013 3:06 pm

Re: Animated 3D models of Steam Engines

Postby bejant » Sat Dec 20, 2014 6:43 pm

Quite a cool project - my compliments to you on your machining and your FreeCAD abilities!

I ran the macro and recorded the animation; anyone can have a look: ... e.mp4?dl=0
User avatar
Posts: 70
Joined: Fri Oct 24, 2014 9:19 pm

Re: Animated 3D models of Steam Engines

Postby TT-RS » Sat Dec 20, 2014 7:33 pm

Very impressive model.
Posts: 33
Joined: Sun Nov 02, 2014 4:21 pm

Re: Animated 3D models of Steam Engines

Postby romartin » Mon Dec 22, 2014 8:28 am

microelly2 wrote:Influenced from this model I have extended and improved the functionality of my plugger object.
Hi microelly2.
I read your post with interest (and viewed the video), but am not sure what you mean by a Plugger. Is it a tool to produce an animation of a set of objects so that one can view them from all angles? Or is it aimed at facilitating animation of moving parts relative to each other and to other fixed parts?
PS. Sorry I took a while to reply but my PC went on strike for three days and I only managed to persuade it to work again an hour ago. I honestly dont know whether the problem was Windows or firmware. Upshot: Ubuntu is looking more attractive, but I've still got cold feet.
Posts: 33
Joined: Sun Nov 02, 2014 4:21 pm

Re: Animated 3D models of Steam Engines

Postby romartin » Mon Dec 22, 2014 8:32 am

Hi bejant and TT-RS,
Thank you for your comments.
Thank you too bejant for recording the animation. Please teach me how to do this.