Mach4 Scripting Manual
User Manual: Pdf
Open the PDF directly: View PDF .
Page Count: 33
Download | |
Open PDF In Browser | View PDF |
Mach4 CNC Controller Lua Scripting Guide 1 Copyright © 2014 Newfangled Solutions, Artsoft USA, All Rights Reserved The following are registered trademarks of Microsoft Corporation: Microsoft, Windows. Any other trademarks used in this manual are the property of the respective trademark holder. Table of Contents 1 2 3 Introduction .................................................................................................................................... 3 1.1 Before You Begin...................................................................................................................... 3 1.2 What is Mach4? ....................................................................................................................... 3 1.3 What is a Mach4 Script? ........................................................................................................... 4 Script Editor ..................................................................................................................................... 5 2.1 File Menu ................................................................................................................................. 6 2.2 Edit Menu ................................................................................................................................ 6 2.3 Search Menu ............................................................................................................................ 7 2.4 Debug Menu ............................................................................................................................ 8 2.5 Help Menu ............................................................................................................................... 8 2.6 Toolbar .................................................................................................................................... 9 Types of Scripts ................................................................................................................................ 9 3.1 M codes ................................................................................................................................... 9 3.1.1 Scriptable M codes .................................................................................................................. 9 3.1.2 M Code Macro Folder............................................................................................................ 10 3.2 Screen Scripts......................................................................................................................... 11 3.2.1 Screen Load Script ................................................................................................................. 11 3.2.2 Screen Unload Script ............................................................................................................. 11 3.2.3 PLC Script .............................................................................................................................. 11 3.2.4 Signal Script .......................................................................................................................... 12 3.3 4 5 Panels .................................................................................................................................... 13 Registers........................................................................................................................................ 14 4.1 Creating Registers .................................................................................................................. 14 4.2 Viewing Registers ................................................................................................................... 15 4.3 Using Registers in Scripts ........................................................................................................ 15 4.4 Saving Registers to Machine.ini .............................................................................................. 17 4.5 Loading Registers From Machine.ini ....................................................................................... 18 Examples ....................................................................................................................................... 18 5.1 Using Signals .......................................................................................................................... 18 2 5.2 Reading Data from an External File......................................................................................... 19 5.3 Tool Change ........................................................................................................................... 20 5.4 Automatic Tool Height Setting ................................................................................................ 25 5.5 Wizards .................................................................................................................................. 27 5.6 Modbus.................................................................................................................................. 33 1 Introduction The purpose of this manual is to teach the basics of scripting in Mach4 using the Lua interface. Scripts add functionality to Mach4 by providing the user with an interface to create custom M codes, macros to control tool changers and other custom accessories, create programming wizards, custom button functions, and much more. This manual will provide some basic programming knowledge as it pertains to creating scripts for Mach4. For more advanced Lua programming there are a variety of resources available online and in print. Note: Operators should be familiar with general CNC and machining practices before operating any CNC machine. Some great resources for additional learning at the machsupport.com forums, other user groups/forums, and books such as CNC Programming Handbook and CNC Control Setup for Milling and Turning, both by Peter Smid and Programming of CNC Machines by Ken Evans 1.1 Before You Begin Any machine tool is potentially dangerous. Computer controlled machines are potentially more dangerous than manual ones because, for example, a computer is quite prepared to rotate an 8" unbalanced cast iron four-jaw chuck at 3000 rpm, to plunge a panel-fielding router cutter deep into a piece of oak, or to mill away the clamps holding your work to the table. Because we do not know the details of your machine or local conditions we can accept no responsibility for the performance of any machine or any damage or injury caused by its use. It is your responsibility to ensure that you understand the implications of what you design and build and to comply with any legislation and codes of practice applicable to your country or state. If you are in any doubt, be sure to seek guidance from a professionally qualified expert rather than risk injury to yourself or to others. 1.2 What is Mach4? 3 Mach4 is software that operates on a personal computer to create a powerful and cost efficient CNC controller. It makes up one small piece of a computer numerical control (CNC) machine. Machines can range from basic mills and lathes to wood routers, plasma cutters, multi axis machining centers, quilting machines, anything requiring motion control. The system is capable of interpreting multiple programming languages, the default and most common being G code, to provide instructions for machine movement and other functions. These instructions are passed to an external motion device What is an external motion device? Originally, Mach-series software only worked with the parallel port (via the parallel port driver), which was a standard port on every PC. Technologies have advanced over time, and not only is the parallel port becoming obsolete, but the Windows codebase has changed to the point where it is technical impossible for the parallel port driver to work. An external motion device is a piece of hardware that is an improvement over the parallel port. It enables a PC running Mach3/Mach4 to control outputs and read inputs. They typically communicate with the PC via an Ethernet or USB connection (but are not limited to those two means of communication). In order to control a machine using an external motion device, the developer of the hardware must write a plugin (driver) for that specific device, so no standard USB-to-parallel port adapters will work. There are many devices listed on our Plugins page in the software and download section of our website (www.machsupport.com). which in turn controls all the inputs and output signals and motion. Mach4 is designed to be flexible and adaptable to a wide variety of machines. Part of this flexibility is the ability for hardware and software developers to create addons or plugins for Mach4 to expand its capabilities. Addons are small programs installed into the Mach4 directory that give Mach the ability to talk to hardware devices such as motion controllers and pendants, communicate with other software, add additional wizards or conversational machining functions, or anything a developer can dream up. Addons to Mach4 are so diverse it would be impossible to cover them in this manual. The developer should provide detailed information on the installation, configuration and use of their addon or plugin. 1.3 What is a Mach4 Script? Scripts in Mach4 are written in the Lua programming language. Users and OEMs can create scripts to accomplish any number of tasks, limited only by the programmer’s imagination. There are four types of scripts in Mach4: M codes, modules, screen, and panels. M codes, screen scripts and panel scripts are each separate containers for code. They cannot interact with each other directly. For example, a variable or function defined in an M code cannot be used in a script on the screen. However, modules provide a place to put code that can be accessed by the others. An M code or screen script can call and use functions and variables from a loaded module. Registers are a powerful way to pass data between different processes in Mach4 and can be used as a bridge between script types. 4 2 Script Editor Mach4 includes a built in script editor. The editor can be found in the ‘Operator’ menu as ‘Edit/Debug Scripts’ (see Figure 2-1). Selecting ‘Edit/Debut Scripts’ will open a window to select the script to be edited. By default the ‘Macros’ folder for the current profile will be shown. Select and open a file and it will be opened in the editor. Figure 2-1: Operator Menu The script editor is essentially a fancy text editor, with some features specific to programming scripts and macros for Mach4. Figure 2-2: Script Editor 5 2.1 File Menu Figure 2-3: Script editor file menu. The file menu contains the controls “New”, “Open”, “Close”, “Save” and “Exit”. Selecting new will open a blank file for creating a new script. Open will open a window to allow the user to find and select an existing script to edit. The close option will close the currently active file. Save allows the user to save the script, there is also a “Save As” option that will can save the document as a new name or in a new location. Exit simply closes the editor and any open files. 2.2 Edit Menu Figure 2-4: script editor edit menu. The edit menu contains the typical Cut, Copy, Paste, Select All, Undo/Redo controls as well as a couple specific to the script editor. When Auto Complete Identifiers is checked the auto complete window will display when typing text in the editor. If the text being typed partially matches known commands then the window will display the possible options. When Auto Complete Identifiers is not checked the auto complete window will not be displayed automatically, but it can be manually shown with the Complete Identifier (Ctrl+K) command. Comment/Uncomment will either add the “- -“ characters to selected text to change it to a comment, or remove the “- -“. 6 Settings displays window that allows the operator to change the color and style of different types of text in the program. Figure 2-5: Script editor settings. 2.3 Search Menu Figure 2-6: Script editor search menu In the search menu the user can find commands to search for and replace text, jump to specific line numbers and sort lines in alphanumerical order. Numbers first, 0 to 9, then letters, A to Z. 7 2.4 Debug Menu Figure 2-7: Script editor debug menu. The debug menu contains the controls for running and debugging programs. Compile compiles the current program into a .mcc file. Run will execute the script. Start Debugging will start the debugger. Debugging helps in diagnosing errors in the program. There are a couple ways to run through the program in the debugging mode. The Start Debugging command will change to Continue when in the debugging mode. When the debugger starts the program will pause at the beginning and wait for input from the user. Continue will run through the complete program. The other controls, Step Into, Step Over, and Step Out allow the user to step through the script line by line. Step Into and Step Over differ in the way functions are executed. Step Into will make a single step into the function. Step Over will execute the entire function and pause at the end. If a function is stepped into, the Step Out command will execute the remaining portion of the function and pause at the end. The Console shows error messages and debugging data while running programs. 2.5 Help Menu The Help Menu displays the About information. 8 2.6 Toolbar Figure 2-8: Script editor toolbar The toolbar in the script editor contains some of the most common controls. From left to right: • • • • • • • • • • • Create new document Open existing document Save current document Save all open documents Cut Copy Paste Undo Redo Search for text Find and replace text 3 Types of Scripts Scripts are divided into 4 types: M codes, screen, panel and modules. This chapter will discuss the differences in and interactions between these types. 3.1 M codes M codes, or miscellaneous functions, are used to create additional functionality in a machine. They are specified in a G code program or in the MDI mode. The functions can range from turning on and off coolant to changing tools, to custom code to engrave a serial number. With the ability to script custom macro M codes the possible functions are as diverse and varied as the machines and operators running them. 3.1.1 Scriptable M codes Scripts cannot be written for all M codes, some have functions that are defined in Mach4 and will only perform that function. Below is a list of the M codes and how they interact with user scripts and internal functions. 9 User Scriptable/No Internal Function M6 M10 to M45 M50 to M61 M66 to M95 M100 and up User Scriptable And Internal Function M3 to M5 M7-M9 M30 M47 Internal Function Only M00 to M02 M46 to M48 M62 to M65 M96 to M99 M codes in the column “User Scriptable/No Internal Function” are completely open to user scripts. There is not function associated to them in Mach4. M codes in the column “User Scriptable And Internal Function” have internal functions in Mach4, but also allow user scripts. These codes are further divided into those that call the function internal to Mach4 OR a user script and those that run both. M3 to M5 and M7 to M9 are codes that control the spindle and coolant functions. If there is no user script for these codes they turn on/off their respective signals as defined inside Mach4. However, if a user script is present, the script will be run instead. This gives the user the power to create custom codes for custom spindle and coolant applications, but if the machine simply needs to turn on/off an output, no programming is required. M30 and M47 are both codes that show up at the end of a program. As they are required to end and/or rewind the G code execution their internal functions cannot be ignored. However, it is useful to have a script execute at the end of a program, a parts counter for instance. For this reason both codes will execute a user script if it is present. After executing the script the M30 and M47 will execute the internal function of ending/rewinding the program. The last column is “Internal Function Only.” M codes found in this column will only execute their internal functions and will NOT execute a user script, even if one is present. 3.1.2 M Code Macro Folder Using custom M codes requires the scripts to be located in the ‘Macros’ folder located in the folder for the desired profile. Navigate to the Mach4 root directory, usually located on the C drive, open the ‘Profiles’ folder, then open the folder of the desired profile. The name of the folder will be the name of the profile, ‘Mach4Mill’ for example. The ‘Macros’ folder will be located in this profile folder. Every script file in this folder will be compiled into one file. If a custom M code is desired it must have an associated file in this folder named in the format M3.mcs, replace the 3 with whatever M code is desired. The format of the script is important as well. Because all the files get compiled into one, each M code must be its own function. function m3() inst=mc.mcGetInstance() mc.mcCntlSetLastError(inst, 'Spindle Clockwise') mc.mcSpindleSetDirection(inst, 1) end if (mc.mcInEditor() == 1) then m3() end 10 Above is an example of a custom script for the M code M3. The name of this file is m3.mcs. Reading through the script the main chunk is the function m3(). This is the function that will be called when an M3 is commanded in a G code file or MDI. The second part of this script is for debugging purposes. When the script is open in the editor nothing would happen when it was run unless there was some code to call the m3() function. However, if there was simply and m3() line to call it, the M code would be executed as soon as Mach4 loads. The if statement checks to see if the script is open in the editor, if it is, then the m3() will be executed. Otherwise the function will need to be called from a G code program or MDI command. 3.2 Screen Scripts The screen contains scripts than run on load and unload and a plc script. Certain screen elements (such as buttons, panels, DROs, and tabs) can also execute user defined scripts. These scripts call all be set in the screen editor (see the Mach4 customization manual for more information of customizing the screen). 3.2.1 Screen Load Script The screen load script runs when the screen is loaded. This is a useful tool for loading saved settings or data, setting a start-up state, initializing controls, etc. Global functions that will be used in other scripts on the screen can also be run in the screen load script. All scripts in the screen (with the exception of panels) will have access to global functions and variables that are defined in the screen load script. This can reduce the amount of programming for the user. A word of caution: The screen load script runs while the screen is being loaded. When trying to set the state of screen elements use care, sometimes the target element has not been loaded when the screen load script runs. If data it to be set on the screen, it is usually best to do that in the first run of the PLC script. 3.2.2 Screen Unload Script The screen unload script runs when the screen is unloaded. This can be a useful tool for saving settings or data. Registers are a perfect example as their values are not saved by Mach4 on exit. The best place to store data with a profile is in the .ini file. Mach4 provides an easy way to do this from a script with the mc.mcProfileWriteString(inst, section, key, value) command. With this command it is possible to write any data to the .ini file to be saved and reloaded later. The register’s section shows specific examples for saving and loading registers from the .ini file. 3.2.3 PLC Script The PLC script continuously runs at an interval set in the screen. By default the PLC scripts run on a 50 millisecond interval. Although this is a script and not a ladder type program, it does provide a similar functionality to a PLC, hence the name. This script can monitor the state of signals and inputs and outputs and react very quickly. A common use for the PLC script is showing errors or faults from external devices such as servo drives and VFDs. 11 The first run of the PLC script is also the best place to set data on the screen for the first time. This ensures that the target element on the screen exists before data is being set. Running a section of the code on the first run is easy to accomplish with a simple counter in the PLC script. count = count + 1 With this count variable at the top of the PLC program it will count up by one every time the PLC script runs. So, on the first run count will equal 1. So an if statement can run certain code only on the first run. 3.2.4 Signal Script The signal script is an all new concept in Mach4. This script is an event handler that can be used to perform actions in response to state changes of signals in the signal library. Signals are internal triggers for events in Mach4 and are not to be confused with external inputs and outputs. Some signals are completely internal and some are used to connect to external inputs and outputs. The signal script can connect all signals to an action. How does it work? A change in state of a signal is considered an event, on every event the signal script runs. In the signal script two variables are used to determine which signal triggered the event and what its new state is. The variable “sig” is the internal ID number of the signal, and the variable “state” is the state of the signal after the event. Now we know which signal triggered the event and what its state is, but we don’t know if it is the signal we want. To do this we need a way of comparing the ID numbers of the signal we want to perform an action and the signal that triggered the event. This means knowing the ID number of the signal we want. Mach4 makes this easy on us by providing a complete set of signal definitions. They are all in the format: mc.OSIG_MACHINE_ENABLED, mc.ISIG_INPUT0, etc. One application of this is to connect physical buttons on a control panel to actions in Mach4. Let’s make a simple cycle start button that is setup on input 1. Since we will be using input 1, the ID number we’ll be looking for is mc.ISIG_INPUT1. In the signal script we could have this code: if (sig == mc.ISIG_INPUT1) then local inst = mc.mcGetInstance() mc.mcCntlCycleStart(inst) end Now, this would work, however remember that the signal script runs for every event. The events are when the signal changes state. So this code would command the cycle start when the button is pressed, and again when it is released. To avoid this we can look for the state to be what we want as well. That would lead us to: if (sig == mc.ISIG_INPUT1) and (state == 1) then local inst = mc.mcGetInstance() mc.mcCntlCycleStart(inst) end SignalTable = { [mc.ISIG_INPUT1] = function (on_off) if (on_off == 1) then 12 mc.mcCntlCycleStart(inst) end end } This code would look for the input 1 signal to change to an active state. When the signal changes to the off state the cycle start will not run. This is relatively simple, but if when connecting a lot of signals the script can get very complex, and checking many if statements can bog things down, slowing the reaction to state changes. To make things run more efficiently we can use a table for all the signals we want to use, and index that table from the signal script. The best place to create the table is in the screen load script, where it will be loaded when Mach4 is started and can then be accessed by the signal script, or any other script in the screen. The table in the screen load script could look something like this: This table contains a function with a name matching the ID number of signal connected to input 1. That function will command a cycle start when the state of the signal is equal to 1, or the signal is active. Now that we have a table we need some code to index it in the signal script. This code, which lives in the signal script, will look into the table, SignalTable, for an entry matching the signal ID number that is stored in the variable sig. If there is no entry nothing is done. If there is, then the state is passed into the function and the desired action is performed. This seems more complex than the if statements at first glance, but it really isn’t. The only code that will be in the signal script is that shown above. The signal table will grow as functions are added in, but it is no more difficult or complex than the many if statements that would be required. And, as said before, the table is much faster to index and thus far more efficient and reactive to events. if (SignalTable[sig] ~= nil) then SignalTable[sig](state) end 3.3 Panels Although panels are located on the screen, they deserve their own section as they are a separate entity. Unlike all the other screen elements they do not share the same global space, and thus cannot access functions or variables in the screen load script like buttons and other controls can. However, they can load and utilize modules just as any other script in Mach4 can. Panels are simply and environment to run a self-contained Lua program. Elements of a panel are not defined in the screen designer, they are defined in the code contained in the panel. The easiest way to create an interface in a panel is to use a form designer capable of outputting Lua code, wxFormBuilder for example. The mouse wheel as MPG code is a great example of how panels can be used. A video tutorial about this code can be found here: https://www.youtube.com/watch?v=MRyaRQwhYWk. A link to the code is in the description. Wizards are another example of what can be shown in a panel. Mach4 comes with an example bolt hole circle wizard that can be displayed in its own dedicated frame or in a panel on the Mach4 screen. The code for this wizard can be found in the “Wizards” folder in the Mach4 directory on your computer, the file name is “BoltHoleCircle.mcs”. 13 4 Registers Registers are a very powerful tool in Mach4. They are completely user definable and can be accessed from anywhere in Mach4. Scripts can use them to record and save data or to transfer data to another script or communicate with a plugin. Registers can contain numbers or strings. 4.1 Creating Registers Registers are created in the Regfile plugin. Select “Plugins…” from the “Configure” menu to display the plugin configuration window, figure 4-1. Figure 4-1: Configure plugins window. In the configure plugins window find the Regfile plugin, row 6 in this case, and click on the “Configure…” button. This will open the register configuration window, figure 4-2. Figure 4-2: Register file configuration window. In this window new registers can be created as well as assigned initial, or default, values. To add a register click on the icon with the green plus sign (top left corner of the tab). A new row will be added, simply give it a name, an initial value, and a description. The name will be used to look up the register, 14 so use something simple. The description is optional but accurate descriptions are certainly beneficial in the long run. The initial value is the value that will be assigned to the register when Mach4 loads. The registers are not saved by default when Mach4 is closed. If a register, or many, needs to be saved on exit the Screen Unload script is a great place to do this. Also, to load the saved value use the Screen Load script. Saving registers to and loading them from the Machine.ini file will be covered in the next sections. 4.2 Viewing Registers The Regfile configuration window shows the registers and their default values. A diagnostics window is provided to show the registers and their values in real time. The diagnostics window can be found by selecting “Regfile” from the “Diagnostic” menu in Mach4. Figure 4-3: Register diagnostics window. The diagnostics window displays all the registers in Mach4 and also provides a display for the pound variables. If the “iRegs0” category is expanded we will see the registers and associated values from the previous section. The register diagnostics window is not limited to only viewing register values, they can be changed as well. Double click on any value and an input window will pop up to allow the user to change the value. 4.3 Using Registers in Scripts There are several commands available for working with registers, for clarity and simplicity we will be using only the necessary few here: • • • • • hreg = mc.mcRegGetHandle(inst, path) val = mc.mcRegGetValue(hreg) string = mc.mcRegGetValueString(hreg) mc.mcRegSetValue(hreg) mc.mcRegSetValueString(hreg) The first step in using registers in scripts is to get the handle. The handle is basically an ID number assigned to the register internally in Mach. There is now what to know this except to ask Mach for it. 15 The mcRegGetHandle function returns the ID or handle. There are two arguments required for this function, the current instance number of Mach we are working in and the path of the register. The instance can be found using the mc.GetInstance function in the format: inst = mc.mcGetInstance() The path is the register type followed by the register name. For example, the path for the first register in figure 4-2 is “iRegs0/Test1.” So to get the handle of the Test1 register the code would look like: inst = mc.mcGetInstance() hreg = mc.mcRegGetHandle(inst, "iRegs0/Test1") Now to get the value. Here we are discussing two ways to retrieve the value, as a number or as a string. If the register’s value is an unknown type then use mcRegGetValueString. Using mcRegGetValue will result in an error if the register contains a string, but mcRegGetValueString can read a number or a string as a string. The only catch there is that math cannot be performed on a string, even if it is a sting of only numbers. Strings containing only numbers can be converted to a number format by using the tonumber(“string”) command. Let’s use mcRegGetValueString to retrieve the value of Test1 and then convert it to a number for computation later. Ofcourse this only works for values that are numbers, the tonumber(“string”) function will create an error if the string contains more than just numbers. inst = mc.mcGetInstance() hreg = mc.mcRegGetHandle(inst, "iRegs0/Test1") val = mc.mcRegGetValueString(hreg) val = tonumber(val) It would save a line of code to use the mcRegGetValue(hreg) function, but there is a reason for using the string method shown above. Every time a register is called it requires two lines of code, as well as the current instance. There is an easier way, create our own function. Remember that all the contents of the macros folder are rolled into one large file, and the screen has access to all the elements of the screen, and the modules can be loaded and used everywhere. With the standard method shown above this code would have to be used every time register information was needed, in every M code, every button, every script in Mach4. Making a function to call the register is much easier and can make the code a lot cleaner. Let’s use a button on the screen as an example. We would like a button to get the value of a register and display it in a message box to the user. Or first step will to be to create a function in the screen load script to get and return the value of the register. This function can be used by our new button and any other button we decide to add to the screen. In the screen load script we will make a function as shown below. function GetRegister(regname) local inst = mc.mcGetInstance() local hreg = mc.mcRegGetHandle(inst, string.format("iRegs0/%s", regname)) return mc.mcRegGetValueString(hreg) end With this function in the screen load script, our button can call it. To get the value of the “Test1” register and display it in a message box the button script will be: regval = GetRegister("Test1") wx.wxMessageBox(regval) 16 Now with that function created, any time we want to retrieve the value of a register we simple call the GetRegister(regname) function with the name of the register to get. In the screen of course. Try adding this function to the screen load script then create a button on the screen to use the GetRegister function. A similar process can be used in the macros. For example a file can be created named “macrofunctions” that contains the GetRegister function and any others that will be frequently used. The functions in this file can be accessed by any of the other macros. Another approach is to use a module. The module can contain these functions then be loaded by the screen load script or in a macro. The functions contained in the module can be used anywhere it is loaded. A function for writing to registers is also useful, and if very similar to the GetRegister function above. function WriteRegister(regname, regvalue) local inst = mc.mcGetInstance() local hreg = mc.mcRegGetHandle(inst, string.format("iRegs0/%s", regname)) mc.mcRegSetValueString(hreg, tostring(regvalue)) end Using WriteRegister is also very simple. WriteRegister("Test1", 12) The example will write a value of 12 to the instance register “Test1”. Again, this function does everything as a string, this way strings or numbers can be sent and set to the registers. If a number is passed into the function it will be converted to a string with the tostring function. 4.4 Saving Registers to Machine.ini Since register values are not saved when Mach is closed it is necessary to create code to save any desired registers. The easiest and most convenient place is in the Machine.ini file. One place for a register save script is in the screen unload script. This script runs when the screen is unloaded/Mach4 is closed. Another place to save registers is in the code immediately following when they are set. The best time and place to save registers to the .ini file will vary depending on the application. To save a register to the .ini file use mc.mcProfileWriteString(). To use this we need to define a section in the .ini file to place the data, specify a name for the data and then the value. For this example let’s assume the register ‘Test1’ equals 6. local inst = mc.mcGetInstance() local hreg = mc.mcRegGetHandle(inst, "iRegs0/Test1") local val = mc.mcRegGetValue(hreg) val = tostring(val) mc.mcProfileWriteString(inst, "Registers", "Test1", val) 17 This little bit of code gets the value of register ‘Test1’ and then writes it to the Machine.ini file in a section labeled as “Registers” with a name “Test1”. The Machine.ini file will contain a section like this: [Registers] Test1 = 6 If the section “Registers” does not exist it will be created, if it does the key “Test1” will be added to it. Writing data to the Machine.ini file is very useful for saving registers, but that is not the only application for it. Any data can be saved for use later, custom screens may have configuration data that needs to be stored, wizards may have information to store for next time, etc. 4.5 Loading Registers From Machine.ini After data is saved to the Machine.ini file it is very useful to retrieve it. For registers, this means that they can be initialized with saved values when Mach4 starts. Like with saving to the Machine.ini file there is a function for loading from it, mc.mcProfileGetString(). For example, to get the previously saved value for “Test1” and set it to the register of the same name the code could be: local inst = mc.mcGetInstance() local val = mc.mcProfileGetString(inst, "Registers", "Test1", "nf") local hreg = mc.mcRegGetHandle(inst, "iRegs0/Test1") mc.mcRegSetValueString(hreg, val) In this code the variable ‘val’ is set to the value found in the Machine.ini file under the section “Registers” and key “Test1”. If there is no value found, ‘val’ will be set to “nf”, the default value we defined in the mc.mcProfileGetString() function. This default value can be defined as anything, it will only be used if there is no value found in the Machine.ini file. A unique value here can be useful to perform a specific action if the key does not exist in the .ini file or if a simple default value is desired. 5 Examples The following examples are to provide some guidance on the creation of scripts. They are not specific to any one machine and are not guaranteed to work on any machine. 5.1 Using Signals Signals are internal triggers for events in Mach4. They can be tied to external I/O, or internal events such as running a Gcode file, jogging, feed hold, soft limit state, enabled state, etc. A common use for signals in scripts will be to read the state of inputs, and set outputs active or inactive. Turning on/off coolant, clamping fixtures, and special spindle functions, are all examples of when signals might be used in a script. Here is a quick script for turning the spindle on, in the forward direction, Mach signal OSIG_SPINDLEFWD. local inst = mc.mcGetInstance() local hsig = mc.mcSignalGetHandle(inst, mc.OSIG_SPINDLEFWD) local spinstate = mc.mcSignalGetState(hsig) if (spindstate == 0) then mc.mcSignalSetState(hsig, 1) 18 else mc.mcSignalSetState(hsig, 0) end To use a signal we first need to know the handle, or Mach’s internal reference ID for the signal. Once we know the handle, the state can be read and/or written to. By reading the state of OSIG_SPINDLEFWD we know if the spindle is on in the forward direction or not. We can then decide what to do based on that information. This script will turn the spindle on in the forward direction if it is currently not running in forward, and will stop it if it is running in forward. This is a very simple example, but it shows the basics of how to use signals. 5.2 Reading Data from an External File Lua scripts are also capable of reading data from an external file, a .csv for example. This can be extremely useful for providing a table of shapes for a wizard, or materials, positions for a tool changer, etc. Having data in an external file can make it easier to edit by providing a formatted Excel spreadsheet for example, or make it more difficult by making the file read only. For an example let’s make a .csv file that contains X, Y and Z positions of tools in a rack style tool Tool_Number,X_Position,Y_Position,Z_Position 1,3.5,2,-10 2,5,2,-10 3,6.5,2,-10 4,8,2,-10 5,9.5,2,-10 6,11,2,-10 changer, we’ll call it “ToolChangePositions.csv”. This is what it will look like: Looking at this table we can see that tool number 1 is at the position X3.5, Y2, Z-10. If we can read this data into a table in a script it can be used to find the tool position in a tool change routine. Below is what that script could look like. For this a table makes the most sense for storing the tool position data. We can start by defining the table TC_Positions. Then we can find the .csv file and store the path to a variable, in this case CSVPath. Next we open the file to read the data. Because we are going to have multiple pieces of data, tool number and three positions, for each entry we will create a table for each entry within the TCPositions table. The variable ToolNum sets the ID of each entry, or line in the .csv. So we start with ToolNum = 0, the header information will land here. Then we increment ToolNum by 1 and run it again, so all the data from the tool number 1 line in the .csv will be in the table TCPositions[1] table and so on. This loop will run until there is no more data in the .csv. Just before incrementing ToolNum, we can set a max tool number in TCPositions so we know how many tools have been defined. We will call this TCPositions[“Max”]. The value of this will be equal to the last tool number entered. So in this example TCPositions[“Max”] = 6. 19 Now the position data can be used in a tool change script. The following lines show how to read and use the data. This example script checks to make sure the selected tool number is greater than 0 but less than or equal to the maximum. So if a user tried to select tool number 10 they would get the “ERROR: Tool number out of range!” message. If the selected tool number is within the valid range then the tool position data will be displayed in the message bar on the Mach4 screen. local TC_Positions = {} local inst = mc.mcGetInstance() local CSVPath = wx.wxGetCwd() .. "\\Profiles\\YourProfile\\Modules\\ToolChangePositions.csv" ToolNum = 0; --[[ Open the file and read out the data --]] io.input(io.open(CSVPath,"r")) local line; for line in io.lines(CSVPath) do tkz = wx.wxStringTokenizer(line, ","); TC_Positions[ToolNum] = {}-- make a blank table in the positions table to hold the tool data local token = tkz:GetNextToken(); TC_Positions[ToolNum] ["Tool_Number"] = token; TC_Positions[ToolNum] ["X_Position"] = tkz:GetNextToken(); TC_Positions[ToolNum] ["Y_Position"] = tkz:GetNextToken(); TC_Positions[ToolNum] ["Z_Position"] = tkz:GetNextToken(); TC_Positions["Max"] = ToolNum --Set the max tool number ToolNum = ToolNum + 1 --Increment the tool number end io.close() --Read tool data local SelectedToolNum = 1 local MaxToolNum = TC_Positions["Max"] if (SelectedToolNum <= MaxToolNum) and (SelectedToolNum > 0) then local Num = TC_Positions[SelectedToolNum].Tool_Number local XPos = TC_Positions[SelectedToolNum].X_Position local YPos = TC_Positions[SelectedToolNum].Y_Position local ZPos = TC_Positions[SelectedToolNum].Z_Position mc.mcCntlSetLastError(inst, string.format("Tool: %.0f | X: %.3f | Y: %.3f | Z: %.3f", Num, XPos, YPos, ZPos)) else mc.mcCntlSetLastError(inst, "ERROR: Tool number out of range!") end 5.3 Modules Modules are scripts that can be accessed from any other script in Mach4. This is useful if you have functions or data that could be used in multiple types of scripts all throughout your Mach interface. This could be reading and storing data for wizards, commonly used functions, or all of the special scripts and functions used in the screen. For developers putting special or custom scripts in a module has the benefit of being able to compile it so pieces of the code cannot be copied and reused somewhere. Screen buttons that require lengthy scripts can be simple function calls to code in a compiled module. For an example, the tool change position script from the previous section could be converted into a module to be used by the tool change routine. This will simplify the tool change script itself and also allow access to the data in other scripts. 20 The first step will be making some slight changes to the tool change positions script from the previous example. It will be saved in the Modules folder as ToolChangePositions.lua. local TC_Positions = {} local inst = mc.mcGetInstance() local CSVPath = wx.wxGetCwd() .. "\\Profiles\\YourProfile\\Modules\\ToolChangePositions.csv" ToolNum = 0; --[[ Open the file and read out the data --]] io.input(io.open(CSVPath,"r")) local line; for line in io.lines(CSVPath) do tkz = wx.wxStringTokenizer(line, ","); TC_Positions[ToolNum] = {}-- make a blank table in the positions table to hold the tool data local token = tkz:GetNextToken(); TC_Positions[ToolNum] ["Tool_Number"] = token; TC_Positions[ToolNum] ["X_Position"] = tkz:GetNextToken(); TC_Positions[ToolNum] ["Y_Position"] = tkz:GetNextToken(); TC_Positions[ToolNum] ["Z_Position"] = tkz:GetNextToken(); TC_Positions["Max"] = ToolNum ToolNum = ToolNum + 1; end io.close() function TC_Positions.GetToolData(SelectedToolNum) local MaxToolNum = TC_Positions["Max"] if (SelectedToolNum <= MaxToolNum) and (SelectedToolNum > 0) then return TC_Positions[SelectedToolNum] else return nil end end return TC_Positions Reading through this new script we can see that it is mostly the same. The difference is an addition of a function, TC_Positions.GetToolData(SelectedToolNum), to return the data associated with the desired tool number back to the main program. This module can stay in the modules folder, and could also be compiled and saved as a .mcc file instead of the .lua or .mcs formats. Any of the three formats are acceptable. A compiled .mcc file has the benefit and drawback of not being editable or even viewable. The function TC_Positions.GetToolData(SelectedToolNum) checks the selected tool number against the max tool number and zero. If the selected tool number is in the valid range then the data is returned using the return command: return TC_Positions[SelectedToolNum]. If the selected tool number is outside the valid range then “nil” is returned. In the main program we can use this difference in returned value to check if the tool number was valid or not. The last line in the module is return TC_Positions. This line sends the table TC_Positions, with all of its contents, back to the script that loaded the module. Now for the main script, this is what would appear in the tool change script to get the tool change positions. local inst = mc.mcGetInstance() package.path = wx.wxGetCwd() .. "\\Profiles\\YourProfile\\Modules\\?.lua;" if(package.loaded.ToolChangePositions == nil) then tcp = require "ToolChangePositions" end local SelectedTool = mc.mcToolGetSelected(inst) 21 ToolData = tcp.GetToolData(SelectedTool) if (ToolData ~= nil) then Num = ToolData.Tool_Number XPos = ToolData.X_Position YPos = ToolData.Y_Position ZPos = ToolData.Z_Position mc.mcCntlSetLastError(inst, string.format("Tool: %.0f | X: %.3f | Y: %.3f | Z: %.3f", Num, XPos, YPos, ZPos)) else mc.mcCntlSetLastError(inst, "ERROR: Tool number out of range!") end The first part of this script set the file path for the module, or package, to load. The “?.lua” is like a wild car. It’s looking for any file with the extension .lua, if you’re module is compiled this would need to be changed to .mcc. The following if statement checks to see if the desired module, in this case ToolChangePositions, is loaded to the variable “tcp”. Whatever is returned at the end of the module, in our module the table “TC_Positions” is returned on the last line, is now contained in the variable tcp. Now tool data can be retrieved by using the TC_Positions.GetToolData(SelectedTool) function. To call it from the main script replace the variable name in the module with the new variable name that the module was loaded to in the main script, tcp. So to get tool data for a selected tool we would call the function tcp.GetToolData(SelctedTool). In this example script the command mc.mcToolGetSelected(inst) is used to get the currently selected tool from Mach, the last T number commanded in a program or in MDI. So in the case of a tool change; if T4 M6 is commanded in a program then calling mc.mcToolGetSelected(inst) in the M6 macro will return a value of 4. Passing this into our GetToolData function will return the position values for the selected tool provided that it is within the acceptable range. Now we can check to see if we got valid data back and use it if we did. If the function returns nil, then we know that the tool number was outside the acceptable range and we can error and/or abort the process. 5.4 Tool Change In the previous examples we’ve been building up to a tool change macro for a rack style tool changer. It’s time to put it all together. We will use the module for getting the tool position data, and the main script from the last example will be the bones of our tool change script. A couple thoughts before we write the script. 1.) A tool change routine should only execute if the tool actually needs changing, there is no sense doing anything if the tool we want to change to is already in the spindle. 2.) We will be moving the machine around and changing modal states and feedrates. It is a good idea to store the state of the machine prior to changing anything so it can be returned to that state when the tool change is complete. This helps to avoid accidents from a machine unknowingly being changed into incremental mode when it is expected to be in absolute, bad feedrates being used because they weren’t specified after a tool change, or any of a host of other possible issues. Best to avoid such problems. Let’s create an m6 tool change script to use our module and change tools. local inst = mc.mcGetInstance() 22 package.path = wx.wxGetCwd() .. "\\Profiles\\YourProfile\\Modules\\?.lua;" if(package.loaded.ToolChangePositions == nil) then tcp = require "ToolChangePositions" end function m6() ------ Get and compare next and current tools -----local SelectedTool = mc.mcToolGetSelected(inst) local CurrentTool = mc.mcToolGetCurrent(inst) if (SelectedTool == CurrentTool) then mc.mcCntlSetLastError(inst, "Next tool = Current tool") do return end end ------ Get current state -----local CurFeed = mc.mcCntlGetPoundVar(inst, 2134) local CurFeedMode = mc.mcCntlGetPoundVar(inst, 4001) local CurAbsMode = mc.mcCntlGetPoundVar(inst, 4003) ------ Get position data for current tool -----ToolData = tcp.GetToolData(CurrentTool) if (ToolData ~= nil) then Num1 = ToolData.Tool_Number XPos1 = ToolData.X_Position YPos1 = ToolData.Y_Position ZPos1 = ToolData.Z_Position else mc.mcCntlEStop(inst) mc.mcCntlSetLastError(inst, "ERROR: Tool number out of range!") do return end end ------ Get position data for next tool -----ToolData = tcp.GetToolData(SelectedTool) if (ToolData ~= nil) then Num2 = ToolData.Tool_Number XPos2 = ToolData.X_Position YPos2 = ToolData.Y_Position ZPos2 = ToolData.Z_Position else mc.mcCntlEStop(inst) mc.mcCntlSetLastError(inst, "ERROR: Tool number out of range!") do return end end ------ Move to current tool change position local GCode = "" GCode = GCode .. "G00 G90 G53 Z0.0\n" GCode = GCode .. string.format("G00 G90 G53 GCode = GCode .. string.format("G00 G90 G53 GCode = GCode .. string.format("G01 G90 G53 mc.mcCntlGcodeExecuteWait(inst, GCode) ------ X%.4f Y%.4f\n", XPos1, YPos1) Z%.4f\n", ZPos1 + 1.0) Z%.4f F15.0\n", ZPos1) ------ Release drawbar -----local DrawBarOut = mc.OSIG_OUTPUT4 local hsig = mc.mcSignalGetHandle(inst, DrawBarOut) mc.mcSignalSetState(hsig, 1) ------ Move to next tool change position -----GCode = "" GCode = GCode .. string.format("G01 G90 G53 Z%.4f\n F15.0", GCode = GCode .. "G00 G90 G53 Z0.0\n" GCode = GCode .. string.format("G00 G90 G53 X%.4f Y%.4f\n", GCode = GCode .. string.format("G00 G90 G53 Z%.4f\n", ZPos2 GCode = GCode .. string.format("G01 G90 G53 Z%.4f F15.0\n", mc.mcCntlGcodeExecuteWait(inst, GCode) ------ Clamp drawbar -----mc.mcSignalSetState(hsig, 0) ------ Move Z to home position ------ 23 ZPos1 + 1.0) XPos2, YPos2) + 1.0) ZPos2) mc.mcCntlGcodeExecuteWait(inst, "G00 G90 G53 Z0.0\n") ------ Reset state -----mc.mcCntlSetPoundVar(inst, 2134, CurFeed) mc.mcCntlSetPoundVar(inst, 4001, CurFeedMode) mc.mcCntlSetPoundVar(inst, 4003, CurAbsMode) ------ Set new tool -----mc.mcToolSetCurrent(inst, SelectedTool) mc.mcCntlSetLastError(inst, string.format("Tool change - Tool: %.0f", SelectedTool)) end if (mc.mcInEditor() == 1) then m6() end Each section of the tool change macro is labeled to make it easier to follow. We’ve combined all the code examples from the previous examples to arrive here, with a couple new additions. Let’s walk through it. The first section of the code loads the module we will be using to look up the tool positions. This is positioned outside the m6() function so that it can be available to all M codes without having to call it in every macro it is used in. Next is the m6() function. Recall that an M code requires a function of the same name to call. In this case an M6 in the program will call the m6() function. Now, inside the function is the meat of the tool change. The first section, “Get and compare next and current tools,” gets the next tool and current tool and compares them to make sure a tool change should happen. If the current tool in the spindle is the tool to be changed to exit the script. Previously we talked about getting the current state of the machine prior to changing anything so we can set it back the way we found it when the script exits. The next section, “Get current state,” retrieves that data for us and saves it for later. The next two sections, “Get position data…,” does just what it says. This is the section of the script that accesses the “ToolChangePositions” module that we created. These two sections function just like the code we developed in the module example. After getting the tool position data it is checked to make sure a valid selection was made. This error also needs to stop the machine or it will just keep running the program when the script is exited, this is big potential for a crash. In this example and E-stop command is given when an invalid tool is selected to make sure the machine is incapable of continuing when the script is exited. This way if the selection is invalid we exit the script with an error rather than get part way thru a tool change and then error for bad data. If the tool selection is valid for both tools we move ahead to the next sections which perform the actual tool change motion. The “Move to current tool change position,” executes a short G code program to move the machine to the spot in the rack for the tool currently in the spindle. The “Release drawbar” section does just that. Using the code from the example in section 5.1 we can make a bit of code to activate the output that controls the drawbar. After releasing the tool the sections, “Move to next tool change position”, “Clamp drawbar”, and “Move to Z home position” continue the rest of the machine motion to pick up the next tool and return home. 24 After all the motion of actually changing tools, we can “Reset state” back to what it was before entering the macro. The last thing to do is reset the current tool to the new tool in the “Set new tool” section. That ends the m6() function. Below that is an if statement to check if the script is open in an editor. As discussed before this is for debugging, so when in the editor the function will be called. 5.5 Automatic Tool Height Setting There are several ways to create an automatic tool height setting process. It could be written in G code or in a Lua script. Since this is a Lua scripting manual we’ll show one way to set tools using a Lua script. This example puts the auto tool height setting script in an M code for easy access from anywhere in Mach. It can be executed from a button or in a program. The following program is an example of an M code for automatically setting the height of a tool. There are a number of necessary parameters that are stored in registers, so it is necessary to get those values before performing the operation. This script follows the same format as the tool change macro example in section 5.4. A brief run through: As before the first step in the script is getting and defining necessary variables and the current state of the machine. Register values are retrieved using the GetRegister() function created in section 4.3. It is not defined in this script so it must be defined in an M code header file or a loaded module to allow access to it. The next sections calculate the position on the touch off sensor and use a G31 probing move the tool into the sensor. The state of the sensor’s signal is checked before movement to make sure it is not already active, and the checked again after it is touched to make sure that it was indeed contacted. This is all error checking and although it is not required it is strongly suggested. Following the actually tool touch off movement, the offset length is calculated and set and then everything returned to its previous state. ------------------------------------------------------------------------------ Auto Tool Setting Macro ------------------------------------------------------------------------------[[ Requires the following instance registers to be defined TS_XPos-----------X position of probe (machine position) TS_YPos-----------Y position of probe (machine position) TS_Type-----------Offset type (1 or 2) TS_TouchPos-------Z position of touch off surface (machine position) TS_ProbeH---------Height of probe above touch off surface TS_DefaultL-------Default tool length guess TS_Retract--------Retract distance after probe touch Offset Type 1-----Length of tool from gauge line to tip Offset Type 2-----Distance from tip of tool to the touch position ]] --The function GetRegister() must be defined for use by macros function m1005() local inst = mc.mcGetInstance() ------------- Define Vars ------------local ProbeSignal = mc.ISIG_DIGITIZE ------------- Get current state ------------local CurTool = mc.mcToolGetCurrent(inst) local CurHNum = mc.mcCntlGetPoundVar(inst, 2032) local CurFeed = mc.mcCntlGetPoundVar(inst, 2134) local CurZOffset = mc.mcCntlGetPoundVar(inst, 4102) 25 local CurFeedMode = mc.mcCntlGetPoundVar(inst, 4001) local CurAbsMode = mc.mcCntlGetPoundVar(inst, 4003) ------------- Get touch off parameters ------------local Xpos = GetRegister("TS_XPos", 1) local Ypos = GetRegister("TS_YPos", 1) local OffsetType = GetRegister("TS_Type", 1) local TouchPos = GetRegister("TS_TouchPos", 1) local ProbeHeight = GetRegister("TS_ProbeH", 1) local RetractDistance = GetRegister("TS_Retract", 1) local ToolLengthGuess = GetRegister("TS_DefaultL", 1) ------------- Check Probe ------------local hsig = mc.mcSignalGetHandle(inst, ProbeSignal) local ProbeState = mc.mcSignalGetState(hsig) if (ProbeState == true) then mc.mcCntlSetLastError(inst, "ERROR: Probe signal is activated") do return end end ------------- Calculations for Gcode ------------local StartHeight = TouchPos + ProbeHeight + ToolLengthGuess + .5 ------------- Generate GCode ------------AutoToolSetGCode = "" AutoToolSetGCode = AutoToolSetGCode .. "G00 G80 G40 G49 G90\n" AutoToolSetGCode = AutoToolSetGCode .. "G00 G53 Z0.0\n" AutoToolSetGCode = AutoToolSetGCode .. string.format("G00 G53 X%.4f Y%.4f\n", Xpos, Ypos) AutoToolSetGCode = AutoToolSetGCode .. string.format("G00 G53 Z%.4f\n", StartHeight) AutoToolSetGCode = AutoToolSetGCode .. "G91 G31 Z-2.0 F25.\n" mc.mcCntlGcodeExecuteWait(inst, AutoToolSetGCode) --Check probe contact ProbeState = mc.mcSignalGetState(hsig) if (ProbeState ~= 1) then mc.mcCntlSetLastError(inst, "ERROR: No contact with probe") mc.mcCntlGcodeExecuteWait(inst, "G0 G90 G53 Z0.0\n") do return end end AutoToolSetGCode = "" AutoToolSetGCode = AutoToolSetGCode .. string.format("G91 G00 Z%.4f\n", RetractDistance) AutoToolSetGCode = AutoToolSetGCode .. "G91 G31 Z-1.0 F10.\n" mc.mcCntlGcodeExecuteWait(inst, AutoToolSetGCode) --Check probe contact ProbeState = mc.mcSignalGetState(hsig) if (ProbeState ~= 1) then mc.mcCntlSetLastError(inst, "ERROR: No contact with probe") mc.mcCntlGcodeExecuteWait(inst, "G0 G90 G53 Z0.0\n") do return end end AutoToolSetGCode = "" AutoToolSetGCode = AutoToolSetGCode .. "G90 G00 G53 Z0.0\n" mc.mcCntlGcodeExecuteWait(inst, AutoToolSetGCode) ------------- Get touch position and set offset ------------local ZProbed = mc.mcCntlGetPoundVar(inst, 5063) local ZOffset = ZProbed - ProbeHeight + CurZOffset if (OffsetType == 1) then ZOffset = math.abs(TouchPos - ZOffset) end mc.mcToolSetData(inst, mc.MTOOL_MILL_HEIGHT, CurTool, ZOffset) mc.mcCntlSetLastError(inst, string.format("Auto tool setting complete, Offset = %.4f", ZOffset)) 26 ------------- Set previous mc.mcCntlSetPoundVar(inst, mc.mcCntlSetPoundVar(inst, mc.mcCntlSetPoundVar(inst, state 2134, 4001, 4003, ------------CurFeed) CurFeedMode) CurAbsMode) end if (mc.mcInEditor() == 1) then m1005() end 5.6 Wizards Wizards are tools that can be created and used to perform common function. In Mach4 a wizard is usually a graphical interface for creating G code files, very similar to conversation programming on some machines. Lua gives the programmer the ability to create a wizard for anything; hole patterns, facing, engraving, spirals, loops, play music. Included with Mach4 is a bolt circle wizard designed to show users how wizards can be built. It is written to be easy for a user to modify to create their own wizard. Wizard scripts should be located in the “Wizards” folder in the Mach4 root directory. The “Load Wizards” button on the default Mach4 screen will load the scripts located here. When the “BoltHoleCircle” wizard is run it will open a window: Figure 5-1: Bolt Hole Circle Wizard This window is created using the functions and variables laid out in the script. There is a lot going on is this script to create the window layout and setup the buttons. Because a lot of the programming for the window itself is out of the scope of this manual, we will stick to discussing how and where to modify this to create another wizard. The only parts to worry about are the functions: Setupinputs(), SaveSettings(), 27 GenGcode(), and variables “m_iniName” and “m_image”. Following is the code to create the bolt circle wizard: ------------------------------------------------------------------------------ Name: BoltHolelua -- Author: B Barker -- Modified by: -- Created: 08/03/2013 -- Copyright: (c) 2013 Newfangled Solutions. All rights reserved. -- Licence: BSD license ----------------------------------------------------------------------------function GetNextID() m_id = m_id+1 return m_id end --global var to hold the frame mainframe = nil panel = nil m_id = 0 m_iniName = "CircleHolePat" ID_GENGCODE_BUT = GetNextID() ID_CLOSE_BUTTON = GetNextID() m_image = wx.wxGetCwd() .. "\\Wizards\\HolesNew.png" function Setupinputs() --Add all the inputs local val m_center_x = AddInputControl("Hole Center X",nil) m_center_x:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Xcenter", "0.000") ) m_center_y = AddInputControl("Hole Center Y", nil) m_center_y:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Ycenter", "0.000") ) m_circle_dia = AddInputControl("Bolt Circle Dia", nil) m_circle_dia:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Dia", "5.0") ) m_NumHoles = AddInputControl("Number Of Holes", nil) m_NumHoles:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Holes", "5") ) m_StartAngle = AddInputControl("Start Angle", nil) m_StartAngle:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Startangle", "0.000") ) m_z_depth = AddInputControl("Hole Depth", nil) m_z_depth:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Depth", "-1.1200") ) m_rapid_height = AddInputControl("Rapid Height", nil) m_rapid_height:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "RapidHeight", "1.000") ) m_retract_height = AddInputControl("Retract Height", nil) m_retract_height:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "RetractHeight", ".100") ) m_peck_depth = AddInputControl("Peck Depth", nil) m_peck_depth:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "PeckDepth", ".125") ) m_feedrate = AddInputControl("Feedrate", nil) m_feedrate:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Feed", "10") ) m_cycletype, ID_CYCLE_TYPE = AddSelectControl("Drill cylce", {"G81 Single pass", "G83 Peck Drill", "G73 High speed Peck"}, ID_CYCLE_TYPE) local val = mc.mcProfileGetString(0 , tostring(m_iniName), "Cycle", "0") m_cycletype:SetSelection(tonumber(val)) m_spindle = AddSelectControl("Spindle Dir", {"None", "CW", "CCW"}, nil) m_Test = AddCheckControl("Check me") m_Test2 = AddRadioControl("Radio killed the video star") AddRadioControl("yes") AddRadioControl("No ") AddRadioControl("okay") end function SaveSettings() mc.mcProfileWriteString(0 , tostring(m_iniName), "Xcenter", tostring(m_center_x:GetValue())) 28 mc.mcProfileWriteString(0 , tostring(m_iniName), "Ycenter", tostring(m_center_y:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "Dia", tostring(m_circle_dia:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "Holes", tostring(m_NumHoles:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "Depth", tostring(m_z_depth:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "RapidHeight", tostring(m_rapid_height:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "RetractHeight", tostring(m_retract_height:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "PeckDepth", tostring(m_peck_depth:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "Feed", tostring(m_feedrate:GetValue())) mc.mcProfileWriteString(0 , tostring(m_iniName), "Cycle", tostring(m_cycletype:GetCurrentSelection())) end function GenGcode() local x_center = m_center_x:GetValue() local y_center = m_center_y:GetValue() local numberofholes = m_NumHoles:GetValue() local dia = m_circle_dia:GetValue() local stAngle = (math.pi/180)*m_StartAngle:GetValue() local RapidHeight = m_rapid_height:GetValue() local retheight = m_retract_height:GetValue() local peck = m_peck_depth:GetValue() local feed = m_feedrate:GetValue() local depth = m_z_depth:GetValue() local gcode = string.format("G00 Z%.4f\n", RapidHeight) local drilltype = m_cycletype:GetCurrentSelection() local x = x_center+(dia/2)*math.cos(stAngle); local y = y_center+(dia/2)*math.sin(stAngle); local spin = m_spindle:GetCurrentSelection() if(spin == 0)then gcode = gcode .. "(No Spindle M Code)\n" elseif(spin == 1) then gcode = gcode .. "M03 (Spinle CW)\n" elseif(spin == 2) then gcode = gcode .. "M04 (Spinle CCW)\n" end if(drilltype == 0)then gcode = gcode .. string.format("G81 X%.4f Y%.4f Z%.4f R%.4f F%.4f\n", x, y, depth, retheight, feed ) elseif(drilltype == 1) then gcode = gcode .. string.format("G83 X%.4f Y%.4f Z%.4f R%.4f Q%.4f F%.4f\n", x, y, depth, retheight, peck, feed ) elseif(drilltype == 2) then gcode = gcode .. string.format("G73 X%.4f Y%.4f Z%.4f R%.4f Q%.4f F%.4f\n", x, y, depth, retheight, peck, feed ) end local StepAng= (2*math.pi)/numberofholes; local i for i=1, numberofholes-1, 1 do x=x_center+(dia/2)*math.cos(stAngle+(StepAng*i)); y=y_center+(dia/2)*math.sin(stAngle+(StepAng*i)); gcode = gcode .. string.format("X%.4f Y%.4f\n", x, y ) end gcode = gcode .. "G80\nM05\nM30" local file = wx.wxFileDialog(panel, "Select Gcode File", "", "", "Text files (*.txt)|*.txt|Tap files (*.tap)|*.tap", wx.wxFD_SAVE,wx.wxDefaultPosition,wx.wxDefaultSize, "File Dialog" ); if(file:ShowModal() == wx.wxID_OK)then local path = file:GetPath() --wx.wxMessageBox(tostring(path)) io.output(io.open(path,"w")) io.write(gcode) io.close() 29 mc.mcCntlLoadGcodeFile( 0, tostring(path)) end SaveSettings() end function main() if(mcLuaPanelParent == nil)then -- create the wxFrame window mainframe = wx.wxFrame( wx.NULL, -- no parent wx.wxID_ANY, -- whatever for wxWindow ID "Mach4 Bolt Hole Wizard", -- frame caption wx.wxDefaultPosition, -- place the frame in default position wx.wxDefaultSize, -- default frame size wx.wxDEFAULT_FRAME_STYLE ) -- use default frame styles -- create a panel in the frame panel = wx.wxPanel(mainframe, wx.wxID_ANY) -- create a simple file menu with an exit local fileMenu = wx.wxMenu() fileMenu:Append(wx.wxID_EXIT, "E&xit", "Quit the wizard") -- create a simple help menu local helpMenu = wx.wxMenu() helpMenu:Append(wx.wxID_ABOUT, "&About", "About Bolt Hole Wizard") -- create a menu bar and append the file and help menus local menuBar = wx.wxMenuBar() menuBar:Append(fileMenu, "&File") menuBar:Append(helpMenu, "&Help") -- attach the menu bar into the frame mainframe:SetMenuBar(menuBar) -- create a simple status bar mainframe:CreateStatusBar(1) mainframe:SetStatusText("No Error.") -- connect the selection event of the exit menu item to an -- event handler that closes the window mainframe:Connect(wx.wxID_EXIT, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) mainframe:Close(true) end ) -- connect the selection event of the about menu item mainframe:Connect(wx.wxID_ABOUT, wx.wxEVT_COMMAND_MENU_SELECTED, function (event) wx.wxMessageBox("Bolt Hole pattern wizard \n\nAuthor: Brian Barker\nDate: 8/3/13\nThis wizard is to be used as an example of how to make a wizard", "About wxLua", wx.wxOK + wx.wxICON_INFORMATION, mainframe) end ) else panel = mcLuaPanelParent end --Set up the main sizer so we can start adding controls local mainSizer = wx.wxBoxSizer(wx.wxVERTICAL) local InputsGridSizer = wx.wxFlexGridSizer( 2, 4, 0, 0 ) InputsGridSizer:AddGrowableCol(1, 0) function AddInputControl(name_string, width) if(width == nil)then width = 100 end local textCtrlID = GetNextID() local staticText = wx.wxStaticText( panel, wx.wxID_ANY, name_string) 30 local textCtrl = wx.wxTextCtrl( panel, textCtrlID, "0.000", wx.wxDefaultPosition, wx.wxSize(width, -1), wx.wxTE_PROCESS_ENTER ,wx.wxTextValidator(wx.wxFILTER_NUMERIC)) InputsGridSizer:Add( staticText, 0, wx.wxALIGN_CENTER_VERTICAL+wx.wxALL+wx.wxALIGN_RIGHT, 2) InputsGridSizer:Add( textCtrl, 0, wx.wxGROW+wx.wxALIGN_CENTER+wx.wxALL+wx.wxALIGN_LEFT, 2) return textCtrl, textCtrlID end function AddCheckControl(name_string) local ID = GetNextID() local staticText = wx.wxStaticText( panel, wx.wxID_ANY, name_string) local Ctrl = wx.wxCheckBox( panel, ID, "", wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxTE_PROCESS_ENTER ,wx.wxTextValidator(wx.wxFILTER_NUMERIC)) InputsGridSizer:Add( staticText, 0, wx.wxALIGN_CENTER_VERTICAL+wx.wxALL+wx.wxALIGN_RIGHT, 2) InputsGridSizer:Add( Ctrl, 0, wx.wxGROW+wx.wxALIGN_CENTER+wx.wxALL+wx.wxALIGN_LEFT, 2) return Ctrl, ID end function AddRadioControl(name_string) local ID = GetNextID() local sizer = wx.wxBoxSizer( wx.wxHORIZONTAL ) local Ctrl = wx.wxRadioButton( panel, ID, name_string, wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxTE_PROCESS_ENTER ,wx.wxTextValidator(wx.wxFILTER_NUMERIC)) InputsGridSizer:Add( sizer, 0, wx.wxALIGN_CENTER_VERTICAL+wx.wxALL+wx.wxALIGN_RIGHT, 2) InputsGridSizer:Add( Ctrl, 0, wx.wxGROW+wx.wxALIGN_CENTER+wx.wxALL+wx.wxALIGN_LEFT, 2) return Ctrl, ID end function AddSelectControl(name_string, selections, selCtrlID) local selCtrlID = GetNextID() local staticText = wx.wxStaticText( panel, wx.wxID_ANY, name_string ) local selCtrl = wx.wxComboBox(panel, selCtrlID, "", wx.wxDefaultPosition, wx.wxSize(100, -1), selections) selCtrl:SetSelection(0) InputsGridSizer:Add( staticText, 0, wx.wxALIGN_CENTER_VERTICAL+wx.wxALL+wx.wxALIGN_RIGHT, 2) InputsGridSizer:Add( selCtrl, 0, wx.wxGROW+wx.wxALIGN_CENTER, 2) return selCtrl, selCtrlID end -- Add image to the top local hbmp = wx.wxBitmap(m_image) local TopImage = wx.wxStaticBitmap(panel, wx.wxID_ANY, hbmp ) --Setup the inputs Setupinputs() -- make the bottom buttons local buttonSizer = wx.wxBoxSizer( wx.wxHORIZONTAL ) local genGcode = wx.wxButton( panel, ID_GENGCODE_BUT, "&PostGcode") genGcode:SetBackgroundColour(wx.wxColour(0,255, 128)) buttonSizer:Add( genGcode, 0, wx.wxALIGN_CENTER+wx.wxALL, 2 ) if(mcLuaPanelParent == nil)then local closeButton = wx.wxButton( panel, ID_CLOSE_BUTTON, "E&xit") buttonSizer:Add( closeButton, 0, wx.wxALIGN_CENTER+wx.wxALL, 2 ) end --Set up the sizers mainSizer:Add( TopImage, 0, wx.wxALIGN_CENTER+wx.wxALL, 2 ) mainSizer:Add( InputsGridSizer, 0, wx.wxALIGN_CENTER+wx.wxALL, 2 ) mainSizer:Add( buttonSizer, 0, wx.wxALIGN_CENTER+wx.wxALL, 2 ) panel:SetSizer( mainSizer ) panel:Connect(ID_GENGCODE_BUT, wx.wxEVT_COMMAND_BUTTON_CLICKED, function(event) GenGcode() end) panel:Connect(ID_CYCLE_TYPE, wx.wxEVT_COMMAND_COMBOBOX_SELECTED, 31 function(event) if(m_cycletype:GetCurrentSelection() == 0)then m_peck_depth:SetEditable(false) m_peck_depth:SetBackgroundColour(wx.wxColour("LIGHT GRAY")) else m_peck_depth:SetEditable(true) m_peck_depth:SetBackgroundColour(wx.wxColour(wx.wxNullColour)) end end) -- Connect a handler for pressing enter in the textctrls panel:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_TEXT_ENTER, function(event) -- Send "fake" button press to do calculation. -- Button ids have been set to be -1 from textctrl ids. -- dialog:ProcessEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, event:GetId()1)) end) -- show the frame window if(mcLuaPanelParent == nil)then panel:Connect(ID_CLOSE_BUTTON, wx.wxEVT_COMMAND_BUTTON_CLICKED, function(event) mainframe:Destroy() end) panel:Fit() mainframe:Fit() mainframe:Show(true) end end main() wx.wxGetApp():MainLoop() First is “m_iniName”. Variable values from the input boxes will be stored in a section in the machine.ini file. Set “m_iniName” to be something descriptive, usually similar to the name of the wizard. This helps with organization in the .ini file. m_iniName = "CircleHolePat" Next is “m_image.” This variable sets the path to the image to be displayed at the top of the wizard window. Change the file path and name to match the location of the desired image. m_image = wx.wxGetCwd() .. \\Wizards\\HolesNew.png The first function is Setupinputs(). This function defines the inputs to be displayed at the bottom of the wizard window. The formatting is already done, all that is required is to add or remove lines and rename them to the desired variable and description. Generally each input will have two lines of code, the first calls the function AddInputControl() to add the label and input box to the wizard window. In the snippet below “m_center_x” will be assigned the value of whatever is input into the input box labeled “Hole Center X”. The second line sets the value of “m_center_x” and the input box to the last value that was saved in the machine.ini file, in the “CircleHolePat” section with the handle “Xcenter”. If there is no value in the .ini then a default value of 0.00 will be assigned. m_center_x = AddInputControl("Hole Center X",nil) m_center_x:SetValue( mc.mcProfileGetString(0 , tostring(m_iniName), "Xcenter", "0.000") ) 32 Drop down selection boxes are also possible using the following format: m_spindle = AddSelectControl("Spindle Dir", {"None", "CW", "CCW"}, nil) Radio controls and check boxes also have functions for easy creation. m_Test = AddCheckControl("Check me") m_Test2 = AddRadioControl("Radio killed the video star") 5.7 Modbus 33
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.5 Linearized : Yes Author : Tom Lamontagne Company : Create Date : 2015:04:20 15:39:44-04:00 Modify Date : 2015:04:20 15:40:00-04:00 Source Modified : D:20150420193901 Tagged PDF : Yes XMP Toolkit : Adobe XMP Core 5.2-c001 63.139439, 2010/09/27-13:37:26 Metadata Date : 2015:04:20 15:40-04:00 Creator Tool : Acrobat PDFMaker 10.1 for Word Document ID : uuid:f0d381c4-9647-4b69-a7b9-2ab9de2252cb Instance ID : uuid:83ab0c5d-b4cd-48b2-8a88-ee42262ac407 Subject : 2 Format : application/pdf Creator : Tom Lamontagne Producer : Adobe PDF Library 10.0 Page Layout : OneColumn Page Count : 33EXIF Metadata provided by EXIF.tools