Coding the Macro
This chapter covers the actual coding of the macro, based on the pseudocode created in the previous chapter. The macro code will be developed in sections, to make it easier to understand and to test. Each additional feature will be integrated into the macro code written for the previous feature.
This macro will make use of a number of built-in macro commands and functions. However, the focus of this tutorial is on the actual syntactical coding of a macro, not on describing each and every macro command. From the Learning SmartCAM table of contents, refer to the Automation/Customization - MCL Reference section, the child documents Reference: Macro Commands and Reference: Macro Functions topics contain descriptions of the macro commands.
The macro will be named: C:\MACROS\PATHLEN.MCL
Initial Checks
There are two conditions that the macro needs to validate before jumping into the main loop and scanning the database. In pseudocode these are:
// Check that a process model is loaded. If not error out
IF last element number is 0, no file is loaded
Display error: Must have an open PM model with geometry
Exit macro
ENDIF
// Make sure the output file path is valid and usable. Otherwise no point in continuing
IF outputfile can not be opened for Writing
Display error: Output path invalid or read-only. Try another path
Exit macro
ENDIF
From the textual description (the pseudocode) it is easy to see there are two checks that need to be made at the start of the file.
When comparing the actual MCL code to the pseudocode, notice there is not a direct one to one mapping between the two. The pseudocode is a high-level description of the functionality, not a step-by-step human language version of the macro.
Some variables will need to be removed, at the start of the macro. This way you can be sure that any existing variables with the same name do not interfere with the proper execution of the macro. They can be removed individually, such as the following:
VAR_REMOVE[VN="ErrMsg"]
VAR_REMOVE[VN="FileOpen"]
Or, perhaps a better way, is to add a prefix to the variable name. Then use the
VAR_REMOVE_LIST[LST=""]
macro command to remove them all with one
command. We will use the underscore (_) character as the prefix in these examples. You
are able to use the * wildcard, which matches for all strings, as part of the argument. So,
VAR_REMOVE_LIST[LST="_*"]
removes all variables that begin with the
underscore character.
Here is the generated MCL code:
// PATHLEN.MCL
// Calculates toolpath lengths, for each used STEP, in current PM file.
// Outputs lengths to a ASCII text report file
// User Variables
STRING:#OUTFILE="C:\\TEMP\\PATHLEN.TXT" // path\fname.ext of output file
// System Variables
VAR_REMOVE_LIST[LST="_*"]
STRING:#_ErrMsg // contains error message text
INTEGER:#_FileOpen = 0 // is the output file open? Default is FALSE (0). TRUE is 1
// Check that a process model is loaded. If not error out
IF (TOTEL() = 0)
// No geometry - an empty file. Error out
#_ErrMsg = "Macro requires an open Process Model file."
GOTO(ERR)
ENDIF
// Make sure output file path is valid and usable.
// Open file in WRITE mode. If problems, error out
F_OPEN[FN=#OUTFILE, TY="W"] // opens file for writing
// Was there an error opening the file?
IF (F_ERROR() <> 0)
// There was an error. Display text and exit
#_ErrMsg = "Error Opening File: " + #OUTFILE + "\n" + F_ERRSTR()
GOTO(ERR)
ELSE
#_FileOpen = 1
ENDIF
// Error handling
@ERR
// Display the error text and then exit the macro
PAUSE[TX=#_ErrMsg, SR=3] // display 3 row message box
GOTO(DONE)
// Exit the macro
@DONE
// Is the Output file still open? If so, close it before exiting
IF (#_FileOpen = 1)
F_CLOSE[FN=#OUTFILE]
ENDIF
A few things to take note of in this code. First, the macro starts with a header (in comments) that explains what the macro is supposed to do.
The #OUTFILE
variable contains the fully qualified path and filename for the output
report file. To change the path or name, modify this variable. Use a variable for these tasks so that
later the location and name can be changed by modifying a single variable. Not modifying a number
of inline references - this will be more clear when the code for writing the report is generated.
The #_ErrMsg
variable contains the error message text. A choice was made regarding
error messages. In this macro, all errors are handled in one location - the @ERR
section.
This is a preference; it would have been valid to display the error message text in the same
area as the error itself. However, that would have entailed having a number of different PAUSE[]
statements scattered throughout the macro. A choice was made to consolidate into one location, so that
there is only one PAUSE[]
and all the error text content is passed using a variable.
The #_FileOpen
variable keeps track of whether or not the Output file is opened
for writing. There is only once case where this is important, the second test to see whether
the file could be opened for writing. If this test failed, it would jump to the @ERR
section
and then the @DONE
section. These are shared so there needed to be a way of tracking whether
or not the Output file was closed.
In the real-world, the programmer would likely just close the output file in the same IF statement
block where the test was run. This would negate the need for #_FileOpen
. An exception was
made in this case to give the reader an example of how flag variables are used.
#_FileOpen is a flag variable. It exists solely to give some sort of status information - in this instance, whether or not a file is opened.
VAR_REMOVE_LIST[]
is used to remove variables from the SmartCAM variable list. This is done
do make sure all variables defined and used in the macro are of the correct data type and not previously
defined by other macros or other instances of this macro being run.
Main Loop
The "Main" loop is where the SmartCAM database is traversed and each element is checked and/or tallied. As indicated in the pseudocode, the main loop will contain two inner loops; one to check whether the current Step number has already been tallied, and a second to march through the Process Model element data and total up the lengths of elements with a given Step ID.
The pseudocode for this main loop is as follows:
// The main routine.
LOOP through entire database, starting at element 1
Is current element a layer?
IF Yes:
Get Step number and description
// Don't check the same Step number more than once
Is Step number already checked?
IF No,
Add Step and Description to list of checked steps
// From the current Element position, check each remaining element and see if it is the
// same step number as current
LOOP from current element to End of Elements
Check each element
IF Layer
skip
ENDIF
IF Step
Get Step number
IF Step matches current Step
Get length and add to running total
ENDIF
ENDIF
END LOOP
ELSE
// Step is already checked, skip it and move to the next element
ENDIF
ELSE If No
Nothing to do, move to next element
ENDIF
END LOOP
Inside the main loop, as covered, there are two inner loops. The first is to see whether or not a given Step ID has already been checked. The second marches through the database looking for elements with matching Step numbers and then totals the lengths.
Step Already Checked
This macro will use two arrays to keep track of data. The first is an INTEGER array that stores
the Step Numbers; it is #_StepIDArray[]
. When an element is checked, if it is a Step, the
routine reads each element in this array to see if it exists. If it does not exist, it is considered
a new Step and the system drops into the second loop - the loop that checks for other elements with the
same Step number. If it is in the array, that means this Step number has already been checked and
no further effort is needed.
The loop used to check and see if a Step has already been tallied is:
// Have we already checked this step?
#_TmpCtr = 0
WHILE (#_TmpCtr <= #_CurArrayPtr)
IF (#_ElmtStep = #_StepIDArray[#_TmpCtr])
// It matches. This Step has been checked, set StepScan flag and jump out of loop
#_StepScan = 0 // do not scan for this step
GOTO(ExitChkLoop)
ELSE
#_TmpCtr = #_TmpCtr + 1
ENDIF
ENDW
// Loop ran thru entire array. No matches, this is a new Step
#_StepIDArray[#_CurArrayPtr] = #_ElmtStep // save the step number
#_StepScan = 1 // scan for other elements of this Step
#_CurrStep = #_ElmtStep // This is the step ID to check for
// Label used as destination to jump out of WHILE Loop
// When checking to see if Step already totalled up
@ExitChkLoop
ENDIF
#_CurArrayPtr
is a counter that keeps track of the number of elements in the
Step ID array. So the loop starts as 0 and checks to the end of the array. If there is a matching
Step number in the array, then a GOTO
is used to jump out of the test.
If no match is found, that means this is a new Step Number. So the #_StepIDArray[]
is
updated with the new Step Number. A flag variable #_StepScan
is set to 1 which means
the rest of the Process Model needs to be scanned for elements with matching Step numbers.
Elements with same Step Number
The second inner loop scans the Process Model database looking for other elements with the
same Step number. When found, the length is tallied and stored in a second array:
#_StpLenArray[]
.
The database is only scanned when the flag variable #_StepScan
is set to 1. And
#_StepScan is only set to one when there is a new Step to check for.
The following is the MCL code to handle this scan:
// If StepScan is TRUE (1) then scan for other elements with same STEP as
// #_ElmtStep. Get length and add to array
IF (#_StepScan = 1)
// Loop thru remaining PM data
#_TmpCtr = #_CurrCntr
WHILE (#_TmpCtr <= TOTEL())
#_ElmtStep = STEP(#_TmpCtr) // returns Step # or -1 if Layer
IF (#_ElmtStep = #_CurrStep)
// They match. Get length and add to running total
#_StpLenArray[#_CurArrayPtr] = #_StpLenArray[#_CurArrayPtr] + LENE(#_TmpCtr)
ENDIF
// Increment counter
#_TmpCtr = #_TmpCtr + 1
ENDW
// Given STEP has been totaled. Increment counters
#_CurArrayPtr = #_CurArrayPtr + 1
#_StepScan = 0 // turn this off until needed again
ENDIF
Notice the inner loop is not starting at element 1 - the first element in the PM database. Since each element is checked as it is found, in the main loop, the inner loop only needs to scan from the current position in the database to the end. Elements prior to the current location have already been checked.
Output
The last section of the macro is the output, in this instance a report. The report displays the Step Number and the total length of geometry for that step. The Steps are output in sequence order, meaning the first step found in the database is the first step output.
At the end of the report the combined total cut length is listed.
// Database scanned and ready to report
#_CurrCntr = 0
WHILE(#_CurrCntr < #_CurArrayPtr)
// F_WRITE does not currently work with ARRAYS. Store data in temporary variables then output
#_IOUTPUT = #_StepIDArray[#_CurrCntr]
#_DOUTPUT = #_StpLenArray[#_CurrCntr]
F_WRITE[FN=#OUTFILE, FMT="Step: %I : Length = %D3.4~n", VR="IOUTPUT, DOUTPUT"]
// Calculate combined total
#_LenTotal = #_LenTotal + #_StpLenArray[#_CurrCntr]
#_CurrCntr = #_CurrCntr + 1
ENDW
// Output total
F_WRITE[FN=#OUTFILE, FMT="Total Length: %D3.4~n", VR="LenTotal"]
// Done
GOTO(Done)
Output consists simply of writing a formatted output string to the open text file.
The routine simply starts at position 0, in the two arrays, and increments to the last elements in the arrays. At the same time, a counter is updated that totals up all the different cut lengths into a total for all the steps in the file.
Finally, a GOTO
is used to jump down to the end of the macro to exit. This
is needed to avoid running the @ERR
code.
The Finished Macro
The following is the finished macro. This macro code shows all the variables needed and the
related VAR_REMOVE[]
commands used to make sure each variable is declared anew. The
main loop is shown, which simply incrementally steps down through the process model element data,
one element at a time. Then allows the two inner loops to handle the bulk of the work.
// PATHLEN.MCL
// Calculates toolpath lengths, for each used STEP, in the current PM file.
// Output lengths to a ASCII text report file
// User variables
STRING:#OUTFILE="C:\\TEMP\\PATHLEN.TXT" // path/fname of output file
// System variables
VAR_REMOVE_LIST[LST="_*"]
STRING:#_ErrMsg // contains error message text
INTEGER:#_FileOpen = 0 // is the output file open? Default is False (0). TRUE is 1
INTEGER:#_CurrCntr=1 // current element in main loop
INTEGER:#_CurrStep // Current Step id
INTEGER:#_ElmtStep // Step of just read element
INTEGER:#_CurArrayPtr=0 // Point to element in array to update
INTEGER:#_TmpCtr=0 // Temporary counter
INTEGER:#_StepScan=0 // Should macro scan for matching elements?
INTEGER:#_IOUTPUT=0 // Integer temp variable for holding numbers for F_WRITE
DECIMAL:#_LenTotal=0.0 // Total length of all steps combined
DECIMAL:#_DOUTPUT=0.0 // Decimal temp variable for holding numbers for F_WRITE
INTEGER:#_StepIDArray[] = ALLOCATE(500, 0)
DECIMAL:#_StpLenArray[] = ALLOCATE(500, 0.0)
// Initial Checks
// First, check and see if there is an open Process Model and that it has geometry
IF (TOTEL() = 0)
// No geometry - so its an empty file. Error out.
#_ErrMsg = "Macro requires an open Process Model file."
GOTO(ERR)
ENDIF
// Next, open the output file. If problems, error out.
F_OPEN[FN=#OUTFILE, TY="W"] // opens file for writing
// Was there a problem opening the file?
IF (F_ERROR() <> 0)
// There was an error. Build the display message and exit
#_ErrMsg = "Error Opening File: " + #OUTFILE + "\n" + F_ERRSTR()
GOTO(ERR)
ELSE
// File opened. Set FileOpen flag to true
#_FileOpen = 1
ENDIF
// Main Loop
WHILE(#_CurrCntr <= TOTEL())
// Is current element a STEP? If so, get number
#_ElmtStep = STEP(#_CurrCntr) // returns Step # or -1 if layer
IF (#_ElmtStep > 0)
// The element is a Step. See if Step is already totalled up. If so, skip
// if not, scan the remaining database for other matching STEP elements
// Have we already checked this step?
#_TmpCtr = 0
WHILE (#_TmpCtr <= #_CurArrayPtr)
IF (#_ElmtStep = #_StepIDArray[#_TmpCtr])
// It matches. This Step has been checked, set StepScan flag and jump out of loop
#_StepScan = 0 // do not scan for this step
GOTO(ExitChkLoop)
ELSE
#_TmpCtr = #_TmpCtr + 1
ENDIF
ENDW
// Loop ran thru entire array. No matches, this is a new Step
#_StepIDArray[#_CurArrayPtr] = #_ElmtStep // save the step number
#_StepScan = 1 // scan for other elements of this Step
#_CurrStep = #_ElmtStep // This is the step ID to check for
// Label used as destination to jump out of WHILE Loop
// When checking to see if Step already totalled up
@ExitChkLoop
ENDIF
// If StepScan is TRUE (1) then scan for other elements with same STEP as
// #_ElmtStep. Get length and add to array
IF (#_StepScan = 1)
// Loop thru remaining PM data
#_TmpCtr = #_CurrCntr
WHILE (#_TmpCtr <= TOTEL())
#_ElmtStep = STEP(#_TmpCtr) // returns Step # or -1 if Layer
IF (#_ElmtStep = #_CurrStep)
// They match. Get length and add to running total
#_StpLenArray[#_CurArrayPtr] = #_StpLenArray[#_CurArrayPtr] + LENE(#_TmpCtr)
ENDIF
// Increment counter
#_TmpCtr = #_TmpCtr + 1
ENDW
// Given STEP has been totaled. Increment counters
#_CurArrayPtr = #_CurArrayPtr + 1
#_StepScan = 0 // turn this off until needed again
ENDIF
// Done with this element, increment to the next
#_CurrCntr = #_CurrCntr + 1
ENDW
// Database scanned and ready to report
#_CurrCntr = 0
WHILE(#_CurrCntr < #_CurArrayPtr)
// F_WRITE does not currently work with ARRAYS. Store data in temporary variables then output
#_IOUTPUT = #_StepIDArray[#_CurrCntr]
#_DOUTPUT = #_StpLenArray[#_CurrCntr]
F_WRITE[FN=#OUTFILE, FMT="Step: %I : Length = %D3.4~n", VR="IOUTPUT, DOUTPUT"]
// Calculate combined total
#_LenTotal = #_LenTotal + #_StpLenArray[#_CurrCntr]
#_CurrCntr = #_CurrCntr + 1
ENDW
// Output total
F_WRITE[FN=#OUTFILE, FMT="Total Length: %D3.4~n", VR="LenTotal"]
// Done
GOTO(Done)
@ERR
// If jump here, there is an error. Display it and exit
PAUSE[TX=#_ErrMsg, SR=3]
GOTO(DONE)
@DONE
// Is the File still open?
IF (#_FileOpen = 1)
F_CLOSE[FN=#OUTFILE]
ENDIF
PAUSE[TX="The macro is complete.", SR=1]
Enhancements
The macro described above can be considered just a framework. There are a number of enhancements
and improvements that can be made to it. Taking an existing framework and using it as the basis
for further development is called Code Reuse
and it is a common and powerful programming
technique.
The following are some ideas about how this macro can be extended. The actual extensions will be left as an exercise for the reader.
- No Step Geometry
Right now, the macro only checks to see if there is any geometry in the model. If not, it triggers an error message and exits. There is also a case where the process model contains only layer geometry. In this instance, a message box should be displayed and the output file not generated.
One way to handle this would be to check the contents of#_CurArrayPtr
, if the value is 0, that means no Step was ever located. In theOUTPUT
section of the macro do the check and if TRUE, no Step geometry, display a warning instead of generating the report. - Containers
This macro does not handle containers properly. A container wraps multiple homogeneous elements into a single named container element. This named element has no length. Therefore, when the macro runs, if the element being checked is a container, it will not see the geometry contained inside the wrapper. Nor will it be able to extract the length of the elements inside the container.
Modifications to support containers would include checking the current element to see if it is a container. If so, get the total size of the database and then unpack the container. Get the new size of the container and subtract the old total. This gives you the number of elements unpacked from the container. Check the unpacked elements. When the current element counter is beyond the end of the container elements, then repackage the container. - Cut Time
The cut length for each Step is currently calculated. Use theJOS
functions to get the cut speed. Divide the speed into the length to get the cut time for each Step. - Data Export
Instead of writing a simple report with the data, export it in ASCII Delimited format or some other common ASCII file format. Then import the data into a spreadsheet or some other tool. This will allow the user to analyse the results of multiple runs. This can be used to experiment with different toolpath generation techniques and compare and evaluate the results - at least, for this one aspect.
The example macro handles looping, branching, arrays, variable declarations, file input/output, status variables, and counters. With these simple techniques, the only real limitation on the types of utility macros that can be created is imagination.
Related Topics