Python 3D Graphics Tutorial 20: Annotating Analog Clock with Numeric Values

preview_player
Показать описание
You guys can help me out over at Patreon, and that will help me keep my gear updated, and help me keep this quality content coming:

In this video we show step-by-step instructions on how to build a 3D clock face model in Vpython. We will show how to place text on the graphic and how to properly align the numbers on the clock face.

#Python
#Lessons
#Vpython
Рекомендации по теме
Комментарии
Автор

I am legend. I almost folded up because I struggled with symmetry of numbers, started watching video but strike of inspiration hit at the last moment and I figured it out literary 10 second before video hint. Thank you for geat content.

jansimurda
Автор

After seeing the Tutorial I had to tweak my code for the adjustment of numbers which Paul explains at the end.
The modified code is :
textH=clockR/4
myLabel=text(text='Sydney Time', align='center', color=color.orange, height=textH/2, pos=vector(0, 1.1*clockR, -clockT/2), depth=clockT)
num=1
numH=textH/2
for alpha in np.linspace(-(np.pi/6), -2*np.pi, 12):
    alpha=alpha+np.pi/2
    myLabel=text(text=str(num), align='center', color=color.orange, height=numH, pos=vector(.7*clockR*np.cos(alpha), .7*clockR*np.sin(alpha)-numH/2, -clockT/2), depth=clockT*(1.05))
    num=num+1

Sean-lvkh
Автор

from USA import bestTeacher
print(" I'm Legend. I was learning math, but because of You I see the math. WOW! greetings from Poland")

dawidstawowy
Автор

Watching this tutorial made me feel like an apprentice who thinks he knows it all, watching a Master Craftsman at work and realizing that I didn't really know as much as I thought I did.
Yes, my clock does have all the numbers in the right place but my methodology was a bit clunky when compared with yours. Still, I've learned a lot and really enjoyed the journey: many thanks.

alfredcalleja
Автор

I am legend. I did all the planning on paper but ran into problems with the coding and thought I was going to fold up but eventually got the numbers on the clock in the right positions. I was using the 'np.linespace' instead of the 'for' statement. You make it look so simple.

petefontana
Автор

Paul I have done it differently but it works perfectly fine.
My code for the purpose of this homework is :
textH=clockR/4
myLabel=text(text='Sydney Time', align='center', color=color.orange, height=textH/2, pos=vector(0, 1.1*clockR, -clockT/2), depth=clockT)
num=1
for alpha in np.linspace(-(np.pi/6), -2*np.pi, 12):
    alpha=alpha+np.pi/2
    myLabel=text(text=str(num), align='center', color=color.orange, height=textH/2, pos=vector(.7*clockR*np.cos(alpha), .7*clockR*np.sin(alpha), -clockT/2), depth=clockT*(1.05))
    num=num+1

Sean-lvkh
Автор

LEGEND!!!


from vpython import *
import numpy as np
import time

# ClockParameters
clockFaceRad = 15 # radius of face of clock
tickLengthLarge = clockFaceRad / 5 # Length of the major ticks
tickLengthSmall = tickLengthLarge / 2.5 # Length of the minor ticks
tickWidthSmall = clockFaceRad / 75 # Width of the minor ticks
tickWidthLarge = tickWidthSmall * 2.5 # Width of the major ticks
clockInnerRad = clockFaceRad - tickLengthLarge*0.75 # Distance of center of clock to center of the major and minor ticks
clockFaceThickness = clockFaceRad / 20 # Thickness of the clock face

minHandLen = clockFaceRad - tickLengthLarge # Length of the minute hand
hrHandLen = minHandLen*0.75 # Length of the hour hand
secHandLen = minHandLen*1.05 # Length of the second hand

secHandHeight = tickWidthLarge/2 # Height of the second hand
secHandWidth = tickWidthLarge/2 # Width of the second hand
minHandHeight =tickWidthLarge # Height of the minute hand
minHandWidth = tickWidthLarge/2 # Width of the minute hand
hrHandHeight = tickWidthLarge*1.5 # Height of the hour hand
hrHandWidth = tickWidthLarge/2 # Width of the hour hand

secHandColor = vector (1, 0, 0) # Color of the second hand
minHandColor = vector (0.1, 0.1, 0.1) # Color of the minute hand
hrHandColor = vector (0.1, 0.1, 0.1) # Color of the hour hand

tickColor = vector (0, 0.5, 0.5) # Color of the ticks
clockFaceColor = vector (157, 34, 53)/255 # Color of the face

textHeight = clockFaceRad/10 # Height of text
myLabelText = 'My Clock' # Text on clock face
myLabelPos = vector (0, clockFaceRad/4, -clockFaceThickness*0.95) # Position of the text
myLabelColor = color.white # Color of the label text
myLabelDepth = clockFaceThickness # Depth of the label text

hrMarkRad = clockInnerRad*0.8 # Distance from center of label to center of clock
hrMarkTextHeight = tickLengthLarge*0.5 # Height of hour markings
hrMarkTextColor = myLabelColor # Color of hour markings
hrMarkTextDepth = myLabelDepth*1.05 # Depth of hour markings

hubRadius = tickWidthLarge*2 # Radius of the central hub
hubColor = minHandColor # Color of the hub
hubLength = clockFaceThickness*2 # Length of the hub

clockFace = cylinder(axis = vector(0, 0, -1), radius = clockFaceRad, color = clockFaceColor, length = clockFaceThickness, shininess = 0.1)
hub = cylinder(axis = vector(0, 0, 1), radius = hubRadius, color = hubColor, length = hubLength, shininess = 0.1)
myLabel = text(text = myLabelText, pos = myLabelPos, align = 'center', color = myLabelColor, height = textHeight, depth = myLabelDepth)

angleInc = np.pi/30
i = 1
while i < 61:
tickLength = tickLengthSmall
tickWidth = tickWidthSmall
if (i%5 == 0):
tickLength = tickLengthLarge
tickWidth = tickWidthLarge
hrMark = text(text = str(int(i/5)), pos = vector(hrMarkRad*np.sin(angleInc*i), hrMarkRad*np.cos(angleInc*i)-hrMarkTextHeight/2, -clockFaceThickness*0.95), align = 'center', color = hrMarkTextColor, height = hrMarkTextHeight, depth = hrMarkTextDepth)
markClock = box(axis = vector(np.sin(angleInc*i), np.cos(angleInc*i), 0), pos = clockInnerRad*vector(np.sin(angleInc*i), np.cos(angleInc*i), 0), length = tickLength, width = tickWidth, height = tickWidth, color = tickColor)

i = i+1

# Initialization of the clock
myTime = time.localtime(time.time())
k = myTime[5] # get the seconds
prevSec = k # placeholder for previous second value
i = myTime[4]+k/60 # get the minutes
j = myTime[3]*5+i/12 # set the hour hand, taking into account there are 60 increments for the 12 hours and we need to include fractional hours (based on the minute hand)

secHand = box(axis = vector(np.sin(angleInc*k), np.cos(angleInc*k), 0), pos = vector(secHandLen*np.sin(angleInc*k)/2, secHandLen*np.cos(angleInc*k)/2, tickWidthLarge*2.5), length = secHandLen, height = secHandHeight, width = secHandWidth, color = secHandColor)
minHand = box(axis = vector(np.sin(angleInc*i), np.cos(angleInc*i), 0), pos = vector(minHandLen*np.sin(angleInc*i)/2, minHandLen*np.cos(angleInc*i)/2, tickWidthLarge*2.0), length = minHandLen, height = minHandHeight, width = minHandWidth, color = minHandColor)
hrHand = box(axis = vector(np.sin(angleInc*j), np.cos(angleInc*j), 0), pos = vector(hrHandLen*np.sin(angleInc*j)/2, hrHandLen*np.cos(angleInc*j)/2, tickWidthLarge*1.5), length = hrHandLen, height = hrHandHeight, width = hrHandWidth, color = hrHandColor)

while True:

if (prevSec == k): # only update all the hands once every second
myTime = time.localtime(time.time())
k = myTime[5] # get the seconds

else:
prevSec = k # remember previous second value
i = myTime[4]+k/60 # get the minutes
j = myTime[3]*5+i/12 # set the hour hand, taking into account there are 60 increments for the 12 hours and we need to include fractional hours (based on the minute hand)

secHand.axis = vector(np.sin(angleInc*k), np.cos(angleInc*k), 0)
secHand.pos = vector(secHandLen*np.sin(angleInc*k)/2, secHandLen*np.cos(angleInc*k)/2, tickWidthLarge*2.5)
secHand.length = secHandLen

minHand.axis = vector(np.sin(angleInc*i), np.cos(angleInc*i), 0)
minHand.pos = vector(minHandLen*np.sin(angleInc*i)/2, minHandLen*np.cos(angleInc*i)/2, tickWidthLarge*1.5)
minHand.length = minHandLen

hrHand.axis = vector(np.sin(angleInc*j), np.cos(angleInc*j), 0)
hrHand.pos = vector(hrHandLen*np.sin(angleInc*j)/2, hrHandLen*np.cos(angleInc*j)/2, tickWidthLarge)
hrHand.length = hrHandLen

gordonspond
Автор

I did the homework but I calculated the angle for the position in the for loop differently than you did. This is the code that I came up with:


for clockNumber in np.linspace(1, 12, 12):

angle =
clockNumbers=text(text=str(int(clockNumber)), height=clockR/8, align='center', color=color.red
, pos=vector((np.cos(angle)*(clockR-quarterMarkL*2)),(np.sin(angle)*(clockR-quarterMarkL*2)-clockR/16),0))


Hope that this is still a correct way of doing it as it is somewhat different from what you did. I don't want to assume that it is correct only because it did work. I am really enjoying learning python and look forward to learn more. You are great.

ecassar
Автор

I feel like I may have taken the easy way out on this one. In the end it work. Here's what I did.

I created an array with the numbers 1-12
made a counter starting at 0, to reference my array.

Used my major tick POS and for LOOP ("for theta in np.linspace(0, 2*np.pi, 13): ect ect

then in my text i used the array[counter}

for example:

myArray = ["3", "2", "1", "12", "11", "10", "9", "8", "7", "6", "5", "4"]
counter=0

ect)
counter=counter+1

Im going to confirm but im pretty sure when i defined myArray i could of specified a string value and not needed all those ""s

for example: myArray=str(3, 2, 1, 12, , 11....ectect)

NOS
Автор

Thanks Paul👌
im going to do a multi-timezone clock for hw🤔

Meganano
Автор

I AM LEGEND! My clock is a Carolina Time version with more attractive colors. :) The Tar Heels appreciate your teaching me to do this.

cbrombaugh
Автор

I am semi-legend. My clock’s hour-number was not properly aligned. Thanks for teaching on aligning the hour-number.

daviddirac
Автор

I have really tried everything out but something just isn't right. The numbers are in the right position but are all tilted to the left. Can't really figure out why

zahraj
Автор

I AM LEGEND but not as aligned as well I would like. I decided to see if you had a method that is easier before I fidget with the pieces.

chris
Автор

I'm legend. (The way this sounds is weird.)

wendygrant
Автор

I'm a Legend.. but a little behind classes due to family commitments.

woodpython
Автор

I am Legend with two Thumps on the chest

Sean-lvkh
Автор

Legend, I think I spent more time figuring out the alignment than getting the numbers on the screen, I also played with the axis but thought 6's don't look great as 9's

philluvschips
Автор

I am legend.
It took me ages to figure out why the numbers were offset up the face. Such a simple step to subtract half the height! I subtracted half the height as as kind of fudge at first, then it hit me that the alignment is at the bottom of the 'text

Also, I appreciate that Paul is being super methodical with subtracting pi/2 from the initial angle and incrementing it negatively. However, where sin and cos are 90° out of phase, and cos is an even function and sin is odd, you can just use sin and cos the other way round and not use any negatives 🙂


from vpython import *
import numpy as np
import time



# VARIABLES:-

# clock body parameters:
clockrad = 1 # clock radius
clockD = 0.15 # depth of the 'body' of the clock
rimW = clockrad*0.07 # rim width (the rim is to be made up of boxes)
rimD = clockD*1.35 # depth of the rim i.e. the dimension that runs perpendicular to the face
rimsmoothness = 1800 # This is how many boxes make up the rim
offset = clockrad*0.04 # distance between ticks and outer rim
clockbackD = clockrad*0.01 # this creates a 'back of the clock, the same colour as the rim so the ticks can't be seen from the back of the clock
centrepinR = clockrad*0.03
centrepinL = (rimD - clockD)*0.9 # height of the centre pin for the hands to (rotate) around
faceD = (rimD - clockD - centrepinL)/3 # thickness of the 'glass' at the front of the clock

# Text and Tick parameters:
textH = clockrad/8
ref = clockrad/4 # Reference (this was the Major ticks from previous clock editions)
SMtickL = 2*ref/3 # Semi-major tick length
SMtickW = SMtickL/5 # Semi-major tick length
SMtickD = SMtickL/8
mtickL = ref/5 # minor tick length
mtickW = mtickL/6 # minor rick width
mtickD = mtickL/8

# Hand parameters:
sechandL = clockrad - offset - mtickL/2 # second hand length
sechandD = sechandW = sechandL*0.015 # second hand depth and width
sechandH = centrepinL*0.95 - sechandD/2 # height of the second hand above the clock face.
minhandL = sechandL # minute hand length
minhandW = minhandL*0.05 # minute hand width
minhandD = minhandW*0.1 # minute hand depth
minhandH = centrepinL/2 # height of the minute hand above the clock face.
hourhandL = sechandL*2/3 # hour hand length
hourhandW = minhandW*1.2 # hour hand widith
hourhandD = minhandD # hour hand depth
hourhandH = centrepinL*0.1 + hourhandD/2 # height of the hour hand above the clock face.



# ELEMENTS:-

# Clock body including rim, back, and central pin for hands:
clock = cylinder(radius = clockrad, color = color.white, axis = vector(0, 0, 1), length = clockD)
for i in np.linspace(0, 2*pi, rimsmoothness): # Start the boxes from the y axis and go clockwise...
rim = box(length = 2*pi*(clockrad + rimW)/rimsmoothness, height = rimW, width = rimD, pos = vector((clockrad + rimW/2)*sin(i), (clockrad + rimW/2)*cos(i), rimD/2), axis = vector(cos(i), -sin(i), 0), color = color.red)
# (length, height, and width are x, y, and z respectively)
rimround = cylinder(radius = rimW/2, length = 2*pi*(clockrad + rimW)/rimsmoothness, pos = vector((clockrad + rimW/2)*sin(i) - (pi*(clockrad + rimW)/rimsmoothness)*cos(i), (clockrad + rimW/2)*cos(i) + (pi*(clockrad + rimW)/rimsmoothness)*sin(i), rimD), axis = vector(cos(i), -sin(i), 0), color = rim.color)
# The (pi*(clockrad + rimW)/rimsmoothness) elements here are to ensure the cylinders are perfectly aligned with the boxes of the rim.
back = cylinder(radius = (clockrad + rimW), length = clockbackD, axis = vector(0, 0, -1), color = rim.color)
centrepin = cylinder(radius = centrepinR, length = centrepinL, pos = vector(0, 0, clockD), axis = vector(0, 0, 1), color = color.black)
faceplate = cylinder(radius = (clockrad + rimW)*0.95, length = faceD, pos = vector(0, 0, clockD + centrepinL + (rimD - clockD - centrepinL)/3), axis = vector(0, 0, -1), color = vector(0.8, 0.8, 1), opacity = 0.15)

# Numbers:

num = 12
for i in range(12):
if(num > 12):
num = 1
number = text(text = str(num), height = textH, depth = textH/12, align = 'center', color = color.blue, opacity = 0.85)
number.pos = vector((clockrad - textH/2 - offset - SMtickL*1.25)*sin(2*pi*i/12), (clockrad - textH/2 - offset - SMtickL*1.25)*cos(2*pi*i/12) - textH/2, clockD)
# The "-textH/2" after the trig function in the y component of the vector is because align = 'center' aligns the box left and right only, not top and bottom!
num += 1

# Semi Major ticks every hour:
for i in np.linspace(2*pi/12, 2*pi, 12):
SMtick = box(length = SMtickL, height = SMtickW, width = SMtickD, pos = vector((clockrad - SMtickL/2 -offset)*sin(i), (clockrad - SMtickL/2 -offset)*cos(i), (clockD + SMtickD/2)), axis = vector(sin(i), cos(i), 0), color = color.black)

# minor ticks (every minute):
for i in range(60):
if((i % 5) != 0):
mtick = box(length = mtickL, height = mtickW, width = mtickD, pos = vector((clockrad - mtickL/2 -offset)*sin(i*2*pi/60), (clockrad - mtickL/2 -offset)*cos(i*2*pi/60), (clockD + mtickD/2)), axis = vector(sin(i*2*pi/60), cos(i*2*pi/60), 0), color = color.black)

# Hands:
sechand = box(length = sechandL, height = sechandW, width = sechandD, pos = vector(0, sechandL/2, clockD + sechandH), axis = vector(0, 1, 0), color = color.red)
sechandbase = cylinder(radius = centrepinR*1.1, length = sechandD, pos = vector(0, 0, clockD + sechandH - sechandD/2), axis = vector(0, 0, 1), color = sechand.color)
minhand = box(length = minhandL, height = minhandW, width = minhandD, pos = vector(0, minhandL/2, clockD + minhandH), axis = vector(0, 1, 0), color = color.blue)
minhandbase = cylinder(radius = centrepinR*1.1, length = minhandD, pos = vector(0, 0, clockD + minhandH - minhandD/2), axis = vector(0, 0, 1), color = minhand.color)
hourhand = box(length = hourhandL, height = hourhandW, width = hourhandD, pos = vector(0, hourhandL/2, clockD + hourhandH), axis = vector(0, 1, 0), color = color.blue)
hourhandbase = cylinder(radius = centrepinR*1.1, length = hourhandD, pos = vector(0, 0, clockD + hourhandH - hourhandD/2), axis = vector(0, 0, 1), color = hourhand.color)



# This is from tutorial 19:-

#textH = MtickL
#mylabel = text(text = 'Time', align = 'center', color = color.black, height = textH, pos = vector(0, 0, clockD))



while True:
rate(1 + 1/2) # Running the rate() function lowers the CPU load by a factor of ≈80 (≈0.1% compared to ≈8%)!!
t = time.localtime(time.time())
# This array is of the form [year, month, date, hour, minute, second, etc(the rest aren't important here)]
sechand.pos = vector((sechandL/2)*sin(2*pi*t[5]/60), (sechandL/2)*cos(2*pi*t[5]/60), clockD + sechandH)
sechand.axis = vector(sechandL*sin(2*pi*t[5]/60), sechandL*cos(2*pi*t[5]/60), 0)
minhand.pos = + 2*pi*t[5]/3600), (minhandL/2)*cos(2*pi*t[4]/60 + 2*pi*t[5]/3600), clockD + minhandH)
minhand.axis = + 2*pi*t[5]/3600)), minhandL*(cos(2*pi*t[4]/60 + 2*pi*t[5]/3600)), 0)
hourhand.pos = + 2*pi*t[4]/720 + 2*pi*t[5]/43200), + 2*pi*t[4]/720 + 2*pi*t[5]/43200), clockD + hourhandH)
hourhand.axis = + 2*pi*t[4]/720 + 2*pi*t[5]/43200), hourhandL*cos(2*pi*t[3]/12 + 2*pi*t[4]/720 + 2*pi*t[5]/43200), 0)

JackVersey