Custom M-Code (Python)

28 Nov 2017 20:59 #102457 by Todd Zuercher
Lets say I have a global parameter #<_change>. I want to read and change the value of that with a user input dialog box that is popped up by a custom M-code.

My understanding is that the way to to this is with a Python-script, but I can't seem to figure it out. (I am no programmer.)

I would like the pop up to show the current value of my parameter, in the entry field, let me make a change to it if nessisary, then when I click OK, change to the new value.

So, far I've gotten to the point where the window pops up, I can put in a number, and press OK, but I have not been able to access the parameter value. (I can't make it actually do anything.)

Please Log in or Create an account to join the conversation.

29 Nov 2017 15:23 #102487 by andypugh
Replied by andypugh on topic Custom M-Code (Python)
I don't think that this is particularly well (or at all) documented.

But the stdglue toolchange prologue file manipulates named parameters.
Though I think that some of the magic might be hidden in exactly what "self" is in this context.

Please Log in or Create an account to join the conversation.

29 Nov 2017 16:28 #102491 by Todd Zuercher
I can't seem to figure out the magic, and the lack of documentation on the subject isn't helping.

For example: When I try to put "import emccanon" (copied and pasted from into my python script, I get "ImportError: No module named emccanon".

Please Log in or Create an account to join the conversation.

29 Nov 2017 17:42 #102495 by andypugh
Replied by andypugh on topic Custom M-Code (Python)
If your code is part of a remap then you _can_ import emccanon. I have never figured out what the magic is to do it outside of a remap.
So, rather than a custom M-code in the M100-M199 range, you can remap another code of your choice, and then it will (probably) be easier.

Please Log in or Create an account to join the conversation.

29 Nov 2017 19:04 #102501 by Todd Zuercher
I am rather surprised there are not already better facilities in Linuxcnc for doing what I want to do. Perhaps I should start over with a better explanation of what I hope to achieve.

What I want to do is, have a code that I can insert into a g-code file that will:

1. pause execution of the g-code file.
2. look up the value of a parameter (numbered local, named, global, named-global, doesn't really matter).
3. open/pop-up an input dialog window.
4. show the existing value of the chosen parameter from #2, on the input line (as the default input value.)
5. changes the parameter value to whatever the user has replaced the original value with in the input box, closes the window and resumes the g-code file execution when the user clicks OK/enter. (or does nothing if they choose cancel/esc.)

Please Log in or Create an account to join the conversation.

29 Nov 2017 22:06 #102511 by Todd Zuercher
I can not seem to make this work the way I wanted it to (with python directly accessing the parameter values)

But I was able to mostly accomplish what I wanted using a tclsh script posted here.

It isn't as neat and tidy as I would like it to be, and requires me to do more in the g-code and use m66and m68 to get the values in and out of Linuxcnc. But at least it works.

The M-code

# EDIT here to set ::the_pin_name as required:
set ::the_pin_name   motion.analog-in-00

# Note: This file must:
#    have execute permissions (chmod +x this_file_name)
#    be located according to rules for user M codes
#    exist before starting LinuxCNC

set ::prog [file tail $::argv0]

package require Hal
package require Tk
set ::default_value  [hal getp motion.analog-out-00]
set ::value $::default_value

proc go {} {
  if [catch {
    puts "$::prog:$::the_pin_name before: [hal getp $::the_pin_name]"
    hal setp $::the_pin_name $::value
    puts "$::prog:$::the_pin_name  after: [hal getp $::the_pin_name]"
  } msg ] {
    set ::value "FIXME"
    popup "ERROR\n\n$msg"
  } else {
    exit 0
proc abort {} {
  popup "Aborting Gcode program"
  exit 1
proc err_exit {msg} {
  popup $msg
  exit 1
proc popup {msg} {
  puts stderr "$::prog: $msg"
  tk_messageBox \
    -title "$::prog:$::the_pin_name" \
    -type ok \
    -message "$msg"

wm title . "$::prog pin: $::the_pin_name"
wm protocol . WM_DELETE_WINDOW  {puts "$::prog: window close disallowed"}
pack [label .l -text "Enter Value:"] -side left
pack [entry .e -textvar ::value] -side left
bind .e <Return> go
pack [button .b -text Go -command go] -side left
pack [button .a -text Abort -command abort] -side left

And and this is the G-code that goes with it.
o10 if [EXISTS[#<_width>]]
    (debug, _global exists and has the value #<_width>)
o10 else
    (debug, _width does not exist set to default value)
o10 endif
m68 e0 q#<_width>
m66 e0 l0
(debug,_width set to #<_width>)

Please Log in or Create an account to join the conversation.

29 Nov 2017 22:11 #102512 by Todd Zuercher
Right now the M-code script set the title bar name to the hal pin name. In order to make the M-code more generic, and re-usable for multiple purposes, would there be a way to pull the pop-up's title bar text, from something in the G-code a comment or something?

Please Log in or Create an account to join the conversation.

30 Nov 2017 22:15 #102546 by Todd Zuercher
Ok, Since I asked this question originally in the Python section of the forum. Here is a very ugly (Did I for get to mention I know next to nothing about programming) Python scrip that does essentially the exact same thing as the tclsh script I posted above.

import linuxcnc
import hal

h = hal.component("M100")
s = linuxcnc.stat()
c = linuxcnc.command()

aout0 = s.aout[0]

from Tkinter import *

def ok():
   print("First Name: %s" % (e1.get()))

master = Tk()
Label(master, text="First Name").pack(pady=4, padx=10)

v = IntVar()
e1 = Entry(master, text=v)

Button(master, text='Cancel', command=master.quit).pack(pady=4, padx=10, side=RIGHT)
Button(master, text='OK', command=ok).pack(pady=4, padx=10, side=RIGHT)

mainloop( )

For what I want to do this script is only deficient in one primary area. I really, really want this to be a generic input dialog, that can be changed by the G-code that calls it. So I want to be able to set what the labels say from the G-code file. For example, If I wanted to prompt the machine operator to input a length, width, number of rows and number of columns. I could call this same bit of code 4 times, and each time it would have the appropriate set of labels.

Can anyone point me to a possible (not too difficult) way to do that?

Please Log in or Create an account to join the conversation.

01 Dec 2017 02:38 - 02 Dec 2017 00:27 #102549 by andypugh
Replied by andypugh on topic Custom M-Code (Python)
OK, so here is another way to do it.

The G-code
M10 (Radius)
M10 (Length)
M10 (Mass/kg;mass);
M11 (Enter Parameters)
(DEBUG, _radius = #<_radius>)
(DEBUG, _length = #<_length>)
(DEBUG, _mass= #<_mass>)

Will pop up this dialog box:

And then after pressing the button, #<_radius> will hold the value of the first box, #<_length>the second and #<_mass> the value in the third box.
The params are created as global (_name) and have all spaces stripped. The param name is the prompt unless a param name is given after a ; in the comment.

M12 clears the dialog box to start again, or M11 will show the same one again.
M11 (with a comment) will change the dialog title.

Big problem: I can't get the button to close the dialog automatically, G-code execution continues when the X is clicked.
It does now close the dialog box.


In the INI
REMAP = M10 modalgroup=4 python=m10
REMAP = M11 modalgroup=4 python=m11
REMAP = M12 modalgroup=4 python=m12
# import the following Python module
TOPLEVEL= python/
# the higher the more verbose tracing of the Python plugin

Create a new folder called "python" in the same directory as the INI
in that folder put a file called "" that has this content
import remap
Then in another file, called "" put this code
import sys
from interpreter import *
from Tkinter import *

list = []

# Add an entry
def m10(self, **words):
	global list
	if not self.task: return INTERP_OK
	return INTERP_OK

def done(self, dialog, entries):
	for entry in entries:
		val = entry[1].get()
		if val.isdigit:
			self.params[entry[0]] = float(val)

# Show the entries
def m11(self, **words):
	if not self.task: return INTERP_OK
	global list
	entries = []
	row = 1
	if not hasattr(sys, 'argv'):
		sys.argv  = ['']
	dialog = Tk()
	for item in list:
		ret = item.split(";")
		prompt = ret[0]
		if len(ret) == 1:
			param = "_" + prompt.replace(" ", "")
			param = "_" + ret[1].replace(" ", "")
		Label(dialog, text=prompt).grid(row=row)
		entry = Entry(dialog)
		entry.grid(column=1, row=row)
			entry.insert(0, self.params[param])
		entries.append((param, entry))
		row += 1
	Button(dialog, text='OK', command=lambda: done(self, dialog, entries)).grid(row=row, column=0, sticky=W, pady=4)
	return INTERP_OK

def m12(self, **words):
	global list
	list = []
	return INTERP_OK
Last edit: 02 Dec 2017 00:27 by andypugh.
The following user(s) said Thank You: Todd Zuercher, cncbeagle

Please Log in or Create an account to join the conversation.

01 Dec 2017 04:24 #102551 by Todd Zuercher
Thank you very much Andy,
That seems to be exactly what I was asking for.
This might be worthy of adding a page to the wiki.

Please Log in or Create an account to join the conversation.

Time to create page: 0.190 seconds
Powered by Kunena Forum