Gear Animations

Create a library script called animation, and add the following function to generate a basic gear shape in a paintable canvas:
from java.awt import BasicStroke, Polygon
from java.lang.Math import sin, cos, PI
def paintGear(graphics, centerX, centerY, gearDiameter, toothHeight, teethCount, rotationAngle, gearColor):
'''
Arguments:
graphics: The java2D graphics option from the repaint event that is being used to generate this animation
centerX: The x coordinate of the center of the gear
centerY: The y coordinate for the center of the gear
gearDiameter: The widest distance across the center of the gear to the tops of the teeth
innerRadius: The distance from the center of the gear to the base of the teeth
teethCount: The number of teeth to be painted on the gear
rotationAngle: The angle of rotation in radians
gearColor: A java.awt.Color used to fill the body of the gear
Returns:
None
Overview:
Calculates all the corners for the teeth, and adds them to a polygon shape to generate a single gear object that can be painted and outlined
'''
graphics.stroke = BasicStroke(2)
gear = Polygon() # Obviously ~ lol
# Calculate the distance from the center of the gear to bottom and top of each tooth
outerRadius = gearDiameter / 2
innerRadius = outerRadius - toothHeight
# Java requries radians, but the frames of this animation are divided up into degrees,
# ...so divide (2 PI) [the radian equivilant of 360 degrees] by the number of teeth
# ...to get the angle needed to space the teeth evenly around the gear
toothAngle = (2 * PI) / teethCount
# Slightly offset the angles of the inner and outer points
# ...to add seemingly uniform flats to the end and base of the gear
outerHobAngleOffset = .4 # I'm using the word Hob because I once worked in a machine shop,
innerHobAngleOffset = .1 # ...and the machine that cut the gears was called a gear hobber
# Calculate the (x, y) coordinates needed to draw the gear, and add them to the polygon object
for tooth in range(teethCount):
# Calculate the current iteration's tooth angle, and offset it by the given rotation anle
startingAngle = rotationAngle + tooth * toothAngle
# Create a list of 6 polar coordinates using the inner and outer radii
# ...combined with the calculated angle of each tooth
# ...slightly offset to create little flats at the top of each tooth and at the bottom between the teeth
polarCoordinates = [(innerRadius, startingAngle),
(innerRadius, startingAngle + toothAngle * innerHobAngleOffset),
(outerRadius, startingAngle + toothAngle * outerHobAngleOffset),
(outerRadius, startingAngle + toothAngle * (1 - outerHobAngleOffset)),
(innerRadius, startingAngle + toothAngle * (1 - innerHobAngleOffset)),
(innerRadius, startingAngle + toothAngle)]
# Use the polar to cartesian equations to convert the polar coordinates
# ...to the required (x, y) coordinates of the Polygon
for radius, angle in polarCoordinates:
x = int(centerX + cos(angle) * radius)
y = int(centerY + sin(angle) * radius)
gear.addPoint(x, y)
# Set the graphics color for the body of the gear and paint the polygon
graphics.color = gearColor
graphics.fillPolygon(gear)
# Draw an outline around the plygon
graphics.color = system.gui.color(48, 48, 48) # Black was too much on the center hole, so I've slightly greyed it
graphics.drawPolygon(gear)
# Add a center hole to the polygon (x, y, width, height)
centerHoleDiameter = 14 # This should be an even number since it's used to offset the x and y coordinates to center this shape on the gear
centerHoleX = centerX - (centerHoleDiameter / 2) # A diameters is easier for me to visialize, but it has to be divided by two
centerHoleY = centerY - (centerHoleDiameter / 2) # ...and converted to a radius to position the hole correctly with the fillOval method
graphics.fillOval(centerHoleX, centerHoleY, centerHoleDiameter, centerHoleDiameter)
To generate the gears depicted in the video above, also add this function the animation library:
def gratuitousExample(graphics, rotationDegree, setupValueX = 50, setupValueY = 50, setupValueDiameter = 100):
'''
Arguments:
graphics: The java2D graphics option from the repaint event that is being used to generate this animation
rotaionDegree: An integer from 0 to 359 that represents which gear angle is being painted during this frame of the animation
[setupValueX, ...Y, ...Diameter]: Optional integers that can be used to position a new gear.
...Defaults to a gear with a 100 pixel diameter positioned in the top left corner of the canvas
Returns:
None
Overview:
Defines the parameters needed for the paintGear library script to paint and aninmate various gears
'''
# All gears should have the same type of tooth if they are to mesh together perfectly
# I've gone around in circles on this trying to decide what will be the most intuiive for developers wanting to use this function,
# ...and presently, I feel like height and width is easier for us to visualize than the actual machinist terms and methods for measuring gear teeth
toothHeight = 12
toothWidth = 20
# *********************************************************
# Repeat this sequence as needed for any subsequent gears
# Parameters for the driving gear
gearOneCenterX = 100 # Center x coordinate
gearOneCenterY = 100 # Center y coordinate
gearOneDiameter = 185 # How big accross the gear should be
gearOneCircumference = PI * gearOneDiameter # Calculate the circumference of the gear
gearOneTeethCount = int(gearOneCircumference / toothWidth) # ...and use it to determine the tooth count
gearOneRotation = rotationDegree * (PI / 180.0) # Convert the degree integer to radians [rotationDegree can also be referred to as the frame of the animation]
gearOneColor = system.gui.color('darkgrey') # This is the fill color for the body of the gear
# See the last gear for an example of how to use the given setup parameters
# *********************************************************
# Parameters for the gearTwo gear
gearTwoCenterX = 201
gearTwoCenterY = 54
gearTwoDiameter = 55
gearTwoCircumference = PI * gearTwoDiameter
gearTwoTeethCount = int(gearTwoCircumference / toothWidth)
gearTwoRotation = -gearOneRotation * (float(gearOneTeethCount) / gearTwoTeethCount) # Invert the angle since this gear moves opposite the gearOne gear, and ratio the angle by the relative number of teeth
gearTwoColor = system.gui.color('grey')
# Parameters for the gearThree gear
gearThreeCenterX = 282
gearThreeCenterY = 90
gearThreeDiameter = 140
gearThreeCircumference = PI * gearThreeDiameter
gearThreeTeethCount = int(gearThreeCircumference / toothWidth)
gearThreeRotation = gearOneRotation * (float(gearOneTeethCount) / gearThreeTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearThreeColor = system.gui.color('darkgrey')
# Parameters for the gearFour gear
gearFourCenterX = 449
gearFourCenterY = 216
gearFourDiameter = 300
gearFourCircumference = PI * gearFourDiameter
gearFourTeethCount = int(gearFourCircumference / toothWidth)
gearFourRotation = -gearOneRotation * (float(gearOneTeethCount) / gearFourTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearFourColor = system.gui.color('grey')
# Parameters for the gearFive gear
gearFiveCenterX = 263
gearFiveCenterY = 254
gearFiveDiameter = 100
gearFiveCircumference = PI * gearFiveDiameter
gearFiveTeethCount = int(gearFiveCircumference / toothWidth)
gearFiveRotation = gearOneRotation * (float(gearOneTeethCount) / gearFiveTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearFiveColor = system.gui.color('darkgrey')
# Parameters for the gearSix gear
gearSixCenterX = 130
gearSixCenterY = 273
gearSixDiameter = 188
gearSixCircumference = PI * gearSixDiameter
gearSixTeethCount = int(gearSixCircumference / toothWidth)
gearSixRotation = -gearOneRotation * (float(gearOneTeethCount) / gearSixTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearSixColor = system.gui.color('grey')
# Parameters for the gearSeven gear
gearSevenCenterX = 509
gearSevenCenterY = 46
gearSevenDiameter = 80
gearSevenCircumference = PI * gearSevenDiameter
gearSevenTeethCount = int(gearSevenCircumference / toothWidth)
gearSevenRotation = gearOneRotation * (float(gearOneTeethCount) / gearSevenTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearSevenColor = system.gui.color('darkgrey')
# Parameters for the gearEight gear
gearEightCenterX = 454
gearEightCenterY = 37
gearEightDiameter = 50
gearEightCircumference = PI * gearEightDiameter
gearEightTeethCount = int(gearEightCircumference / toothWidth)
gearEightRotation = -gearOneRotation * (float(gearOneTeethCount) / gearEightTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearEightColor = system.gui.color('grey')
# Parameters for the gearNine gear
gearNineCenterX = 401
gearNineCenterY = 37
gearNineDiameter = 70
gearNineCircumference = PI * gearNineDiameter
gearNineTeethCount = int(gearNineCircumference / toothWidth)
gearNineRotation = gearOneRotation * (float(gearOneTeethCount) / gearNineTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearNineColor = system.gui.color('darkgrey')
# *********************************************************
# New Gear setup example:
# I found that generating the optional arguments with spinner components worked quite well
gearTenCenterX = setupValueX # Use the given setup parameters to position the gear at center point X
gearTenCenterY = setupValueY # Use the given setup parameters to position the gear at center point Y
gearTenDiameter = setupValueDiameter # Use the given setup parameters to adjust the diameter in real time as needed to fit the gear
gearTenCircumference = PI * gearTenDiameter
gearTenTeethCount = int(gearTenCircumference / toothWidth)
gearTenRotation = -gearOneRotation * (float(gearOneTeethCount) / gearTenTeethCount) # Take the angle of the gearOne [driving] gear, and ratio this gear's angle by its relative number of teeth
gearTenColor = system.gui.color('orange') # Using an odd color to depict the setup gear helps as well if there are many other gears in the animation
# *********************************************************
# Paint the gears
paintGear(graphics, gearOneCenterX, gearOneCenterY, gearOneDiameter, toothHeight, gearOneTeethCount, gearOneRotation, gearOneColor)
paintGear(graphics, gearTwoCenterX, gearTwoCenterY, gearTwoDiameter, toothHeight, gearTwoTeethCount, gearTwoRotation, gearTwoColor)
paintGear(graphics, gearThreeCenterX, gearThreeCenterY, gearThreeDiameter, toothHeight, gearThreeTeethCount, gearThreeRotation, gearThreeColor)
paintGear(graphics, gearFourCenterX, gearFourCenterY, gearFourDiameter, toothHeight, gearFourTeethCount, gearFourRotation, gearFourColor)
paintGear(graphics, gearFiveCenterX, gearFiveCenterY, gearFiveDiameter, toothHeight, gearFiveTeethCount, gearFiveRotation, gearFiveColor)
paintGear(graphics, gearSixCenterX, gearSixCenterY, gearSixDiameter, toothHeight, gearSixTeethCount, gearSixRotation, gearSixColor)
paintGear(graphics, gearSevenCenterX, gearSevenCenterY, gearSevenDiameter, toothHeight, gearSevenTeethCount, gearSevenRotation, gearSevenColor)
paintGear(graphics, gearEightCenterX, gearEightCenterY, gearEightDiameter, toothHeight, gearEightTeethCount, gearEightRotation, gearEightColor)
paintGear(graphics, gearNineCenterX, gearNineCenterY, gearNineDiameter, toothHeight, gearNineTeethCount, gearNineRotation, gearNineColor)
paintGear(graphics, gearTenCenterX, gearTenCenterY, gearTenDiameter, toothHeight, gearTenTeethCount, gearTenRotation, gearTenColor)
For this example, add the following components to a Vision window:
• A paintable canvas that is at least 620 x 400 pixels.
...Note: If you need your gear animations to scale to any size, see this post.
• A timer component to drive the animation
• Three spinner components for gear setup
• Four labels to make the spinner purposes clear
Add the following custom properties to the paintable canvas:
• frame [integer]
• setupValueDiameter [integer]
• setupValueX [integer]
• setupValueY [integer]
Bind the custom properties to the following component properties:
• frame -- Bound to the timer component's value property
• setupValueX -- Bound to the first spinner component's intValue property
• setupValueY -- Bound to the second spinner component's intValue property
• setupValueDiameter -- Bound to the third spinner component's intValue property
Setup the timer component in the following way, so it counts up almost endlessly and changes its value property approximately every 10 milliseconds
Add the following script to the repaint event handler of the paintable canvas:
# The animation is drawn using 360 frames [one for each degree],
# ...so the animation script will need to know which frame to paint
graphics = event.graphics
frame = event.source.frame % 360
animation.gratuitousExample(graphics, frame, event.source.setupValueX, event.source.setupValueY, event.source.setupValueDiameter)
Set the spinner min, max, and initial values:
• All spinners should have an arbitrarily large value to ensure they never interfere with your ability to adjust the position and size of the gears. I set mine to 100 million
• X and Y setup spinners can have a minimum value of 0, but I use 25 to ensure the gears are always beyond the top and left sides of the canvas
• The diameter spinner's minimum value MUST be big enough to ensure at least one tooth can appear on the gear. Otherwise, a zero division error will occur in the script I've provided. I set mine to 50, so the gear will never start out overlapping the left and top edges of the canvas
• Set the integer values of each spinner to some arbitrary number between the min and max values, or a red overlay will appear over the spinner at run time. For the example setup gear, I've found the following values position the gear well in this animation: X = 348, Y = 28, and Diameter = 57
Start preview mode, and toggle the animation on or off as needed using the timer component's running property

New Gear Setup Notes:
• Once a setup gear is positioned correctly, replace the variable names in the library script with the integer values from the spinners.
• To add another gear, simply create a new parameter set in the library script, and use the optional setup position variables to repeat the setup process
• Obviously, the spinners and their labels can be deleted once the setup process for the animation has been completed.
• See the example function's code comments for additional notes about gear setup
Here is an export of the example project in case anybody wants to play around with it:
gearExample.zip (20.1 KB)
I imagine a lot can be done to improve the level of detail in the gears, such as adding rings to the outside or hub rings around the center hole to give the gears a more 3D look. It would also be nice to add some trapezoidal shapes to give the gears a more spoked appearance,

...so if anybody adds something that improves this example, please reply to this thread, and show us how it's done.



