Clock animation in a drawing with VBA


If you want to create an animation in CATIA, most of the time you use a DMU Kinematics workbench. However, simple animations can be performed without any dedicated CATIA module, just with a plain VBA code. Moreover, you can use VBA across workbenches and Drafting workbench is not an exception. I am going to show you, how you can create a simple animation in a CATIA V5 drawing.


Wanna see it in action?

Then watch this video:

Clock animation in CATIA V5 Drawing (VBA)

Timer procedure

We start with a definition of "module-level" variables and a "timer" procedure MyTimer which is running in a loop and in preset intervals regularly redraw our watch. An important part of this procedure is Windows API function Sleep. This function suspends the execution of the current thread until the time-out interval elapses. The function takes as a parameter a time interval (in milliseconds) for which execution is to be suspended.

Within the loop of the MyTimer procedure, we check if redrawing interval has already passed and if so, we call DrawWatch subroutine. The redrawing interval is set to half of a second and it is stored in an interval variable. Then we suspend execution for 20 milliseconds and call DoEvents function which keeps CATIA user interface more responsive. Theoretically, we can continue even without a call to a Sleep function, but then CATIA and DoEvents function become resources hungry and slow down the PC considerably because there's no pause in the processing.

And what official documentation V5Automation.chm says about DoEvents function? See below:

The general purpose of the VBA function DoEvent is to allow the System to manage input events so that the UI becomes more responsive.

Besides this function is generally dangerous because it can generate unexpected reentrancy. CATIA infrastructure is not architectured to manage event treatment while the process is blocked into a VBA macro execution. Calling DoEvent while a macro is executed can so lead to unpredictable behavior.

V5Automation.chm

Well, it does not sound very optimistic, but we can live with it :). Without DoEvents, CATIA, as well as VBA Editor, remain completely frozen and nonresponsive.

Remark

If you need a more advanced timer solution, you can use timer functions provided by Windows API. Functions SetTimer and KillTimer give you some advantage over our simple "DoEvents" timer, but if used inappropriately, CATIA will likely crash completely and you can lose your unsaved work.



Option Explicit

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Private pi As Double
Private firstDraw As Boolean

Private hourHand As Line2D
Private minuteHand As Line2D
Private secondHand As Line2D

Sub MyTimer()
    Dim myView As DrawingView
    Set myView = CATIA.ActiveDocument.Sheets.ActiveSheet.Views.ActiveView
    
    firstDraw = True
    pi = 4 * Atn(1)
        
    Dim interval As Double
    interval = TimeValue("0:00:01") / 2
    
    Dim startTime As Date
    startTime = Now
    
    DrawFace myView, 50
    
    Do
        If CDate(Now - startTime) > interval Then
            Debug.Print GetHourFraction(Now)
            
            DrawWatch myView, 50
            startTime = Now
        End If
        
        Sleep 20
        DoEvents
    Loop While True
End Sub
    

Drawing procedures

To draw a face of our clock we call a DrawFace sub. This procedure is called only one time before the main timer loop. Elements of the face are not moving (constant in time) and there is no need to redraw them. To create individual elements of the clock face we use a Factory2D object of a drawing view and some basic geometry equations. To generate text we utilize DrawingTexts collection and text format is set by DrawingTextProperties object.


Sub DrawFace(myView As DrawingView, r As Double)
    Dim fact2D As Factory2D
    Set fact2D = myView.Factory2D
    
    Dim i As Long
    Dim coords()
    
    fact2D.CreateClosedCircle 0, 0, r * 1.1
    fact2D.CreateClosedCircle 0, 0, r * 1.15
    
    For i = 1 To 12
        coords = Array(r * 0.9 * Cos(360 / 12 * i * pi / 180), r * 0.9 * Sin(360 / 12 * i * pi / 180))
       
        fact2D.CreateClosedCircle coords(0), coords(1), 2
    Next
    
    Dim digits()
    digits = Array("3", "6", "9", "12")
'    digits = Array("III", "XI", "IX", "XII")
    
    Dim angledigits As Double
    
    ' get drawing text collection
    Dim drwTexts As DrawingTexts
    Set drwTexts = myView.Texts
    
    For i = 1 To 4
        angledigits = pi / 2 - 360 / 4 * i * pi / 180
        coords = Array(r * 0.7 * Cos(angledigits), -r * 0.7 * Sin(angledigits))
        
        ' create letter
        Dim digit As DrawingText
        Set digit = drwTexts.Add(CStr(digits(i - 1)), coords(0), -coords(1))
        
        ' get DrawingTextProperties object of a letter
        Dim textProp As DrawingTextProperties
        Set textProp = digit.TextProperties

        ' set text properties of a letter
        With textProp
            ' set letter mirror mode, do not let CATIA to perform auto flip of text
            .AnchorPoint = catMiddleCenter
            .Bold = True
            .FontName = "Monospac821 BT"
            .FontSize = 7

            ' update text properties
            .Update
        End With
    Next
End Sub
    

You can even choose between arabic and roman numerals by commenting/uncommenting some line(s). I am sure you know which lines I am talking about ☺. When you launch this procedure, you should get the following result:

Clock face arabic numerals

Clock face arabic numerals

Clock face roman numerals

Clock face roman numerals

The rest of the code consists of a procedure DrawWatch which is launched on every timer tick (called from inside of the main timer loop) and it draws/redraws the hands of our clock. Hands are just lines (Line2D object) with different length and thickness. On a first call of the method, we create all required elements (lines) and we set their thickness with the help of a Selection object. Once created, they are stored in module variables hourHand, minuteHand, secondHand, and their position is updated on every tick.

To get a proper position of clock hands we use VBA time functions. VBA represents date and time as a number (ddddd.tttttt). Integer portion represents the number of days since [January 0, 1900]. Any dates before this one are stored as negative numbers, all dates after are stored as positive values. The fractional portion of the date represents the fractional portion of a 24 hour day. For example, 6:00 AM is stored as 0.25, or 25% of a 24 hour day. To convert a 24 hour day to its analog counterpart (12-hour format) we use helper function GetHourFraction. It takes a date as a parameter, strip its integer (days) part and return its decimal portion converted to an analog format.


Function GetHourFraction(t As Date) As Double
    ' 12 hours boundary
    Const BOUNDARY As Double = 0.5
    
    Dim fraction As Double
    fraction = CDbl(t) - Int(t)
    
    If fraction <= BOUNDARY Then
        GetHourFraction = fraction / BOUNDARY
    Else
        GetHourFraction = (fraction - BOUNDARY) / BOUNDARY
    End If
End Function

Sub DrawWatch(myView As DrawingView, r As Double)
    Dim currTime As Date
    currTime = Now
    
    Dim currSecond As Long
    currSecond = Second(currTime)
    
    Dim currMinute As Long
    currMinute = Minute(currTime)
    
    Dim currHour As Long
    currHour = Hour(currTime)
    
    Dim angleSecond As Double
    angleSecond = pi / 2 - 360 / 60 * currSecond * pi / 180
    
    Dim angleMinute As Double
    angleMinute = pi / 2 - 360 / 60 * currMinute * pi / 180
    
    Dim angleHour As Double
    angleHour = pi / 2 - 360 * GetHourFraction(currTime) * pi / 180
    
    Dim coordsSecond()
    coordsSecond = Array(r * Cos(angleSecond), r * Sin(angleSecond))
    
    Dim coordsMinute()
    coordsMinute = Array(r * 0.8 * Cos(angleMinute), r * 0.8 * Sin(angleMinute))
    
    Dim coordsHour()
    coordsHour = Array(r * 0.6 * Cos(angleHour), r * 0.6 * Sin(angleHour))
    
    Dim fact2D As Factory2D
    Set fact2D = myView.Factory2D
    
    If secondHand Is Nothing Then
        Set secondHand = fact2D.CreateLine(0, 0, coordsSecond(0), coordsSecond(1))
        Set minuteHand = fact2D.CreateLine(0, 0, coordsMinute(0), coordsMinute(1))
        Set hourHand = fact2D.CreateLine(0, 0, coordsHour(0), coordsHour(1))
    Else
        secondHand.SetData 0, 0, coordsSecond(0), coordsSecond(1)
        minuteHand.SetData 0, 0, coordsMinute(0), coordsMinute(1)
        hourHand.SetData 0, 0, coordsHour(0), coordsHour(1)
    End If
    
    If firstDraw Then
        ' get drawing doc selection
        Dim sel As Selection
        Set sel = CATIA.ActiveDocument.Selection
'        Set sel = myView.Parent.Parent.Parent.Parent.Selection
        
        ' clear the selection
        sel.Clear
        sel.Add minuteHand
        
        ' change the visible width to the required value
        Dim visProp As VisPropertySet
        Set visProp = sel.VisProperties
        visProp.SetVisibleWidth 4, 0
        
        sel.Clear
        sel.Add hourHand
        
        Set visProp = sel.VisProperties
        visProp.SetVisibleWidth 8, 0
        
        sel.Clear
    End If
    
    firstDraw = False
End Sub
    

To test it, copy a source code into a standard module and launch MyTimer procedure.

A complete source can be found here Clock animation source
Watch an application video here Clock animation in CATIA V5 Drawing (VBA)



Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>