vlachoudis / bCNC

GRBL CNC command sender, autoleveler and g-code editor
GNU General Public License v2.0
1.56k stars 532 forks source link

gcode parameters (#...=...) looks as processed incorrectly #334

Open bhgv opened 8 years ago

bhgv commented 8 years ago

i tryed to use bcnc with geda pcb g-code. it contains many strings like:

#102=8.3  (comment 1)
#103=300.0  (comment 2)
..
..
F#103

after run it, bcnc showed me "error: Undefined feed rate" and stoped. i uncommented string

Sender.py:725 -> def serialIO(self):  ...  print "+++",repr(tosend)

and it wrote into console

+++ 'F0\n'
# should be F300.0

can anyone point me where it processes parameters? i tryed to find.

cnc.py: 1567 -> def compile(program):
--//-- : 988 -> def parseLine2(line, space=False):
--//-- : 1022 ->        if line[0]=='_':            try:    return compile(line,"","exec")

this point my debugger go to 1026

except:
                # FIXME show the error!!!!
                return None

is the compile here - https://docs.python.org/2/library/functions.html#compile ? the line value here is:

_103=300.0  (comment 2)

and python compiler gives a error.

ok, will continue in the next post

PS (maybe it would be better to write non-handwrited parser? i can help if you want)

bhgv commented 8 years ago

i tryed to change cnc.py: 1021

        # most probably an assignment like  #nnn = expr
        if line[0]=='_':
            line = re.sub("\([^\)]*\)", "", line)  # this is my correction
            try:
                return compile(line,"","exec")
            except:
                # FIXME show the error!!!!
                return None

now line:

_103=300.0

and compile without exception, but anyway on sender.py -> serialIO -> tosend

+++ 'F0\n'

ok, will continue in the next post (i afraid of bsod)

bhgv commented 8 years ago

..continue

ok, cnc.py: 1139 -> def motionStart(self, cmds): --//-- : 1170

            elif c == "F":       # cmd = "F#103"
                self.feed = value*self.unit  # value = 0

but, --//-- : 1143

            try: 
                value = float(cmd[1:])  # cmd[1:] = "#103"
            except:
                value = 0  # and value == 0 with any parameter

ok, lets try to repire in the next post

bhgv commented 8 years ago

in the "GCode.vars" dict are all the parameters, but i can't receive them from cnc.py:1139 Cnc.motionStart !

bhgv commented 8 years ago

changes to support parameters

cnc.py

117: (global space)

GCODE_PARAMS = {}

def value_of_cmd(cmd):
    value = 0.0
    try:
        if cmd[1] == '_':
            pat = re.match("^_\d+", cmd[1:])
            if pat and pat.group(0) in GCODE_PARAMS.keys():
                value = float(GCODE_PARAMS[pat.group(0)])
        else:
            value = float(cmd[1:])
    except:
        value = 0.0
    return value

~2140: (GCode class)

class GCode:
    LOOP_MERGE = False

    #----------------------------------------------------------------------
    def __init__(self):
        global GCODE_PARAMS

        self.cnc = CNC()
        self.header   = ""
        self.footer   = ""
        self.undoredo = undo.UndoRedo()
        self.probe    = Probe()
        self.orient   = Orient()
        self.vars     = {}      # local variables
        self.init()

        GCODE_PARAMS = self.vars  # my horrible hack

~735

    @staticmethod
    def updateG():
        for g in CNC.vars["G"]:
            if g[0] == "F":
                CNC.vars["feed"] = value_of_cmd(g) #float(g[1:])
            elif g[0] == "S":
                CNC.vars["rpm"] = value_of_cmd(g) #float(g[1:])
            elif g[0] == "T":
                CNC.vars["tool"] = value_of_cmd(g) #int(g[1:])
            else:
                var = MODAL_MODES.get(g)
                if var is not None:
                    CNC.vars[var] = g

~1156:

    def motionStart(self, cmds):
        #print "\n<<<",cmds
        for cmd in cmds:
            c = cmd[0].upper()
            value = value_of_cmd(cmd)  # my change
#           try: 
#               value = float(cmd[1:])
#           except:
#               value = 0

            if   c == "X":

~1604

            for cmd in cmds:
                c = cmd[0]
                value = value_of_cmd(cmd)  # my change
                #try: float(cmd[1:])
                #except: value = 0.0
                if c.upper() in ("F","X","Y","Z","I","J","K","R","P"):
                    cmd = CNC.fmt(c,value)

~3964:

                for cmd in cmds:
                    c = cmd[0]
                    value = value_of_cmd(cmd)  # my change
                    #try: float(cmd[1:])
                    #except: value = 0.0
                    if c.upper() in ("F","X","Y","Z","I","J","K","R","P"):
                        cmd = self.fmt(c,value)
                    else:

this is not a finish. continue will be tomorrow

bhgv commented 8 years ago

with changes from the post https://github.com/vlachoudis/bCNC/issues/334#issuecomment-221455655 it fixes parameters, but looks like grbl doesn't like F0 commands. try to remove or change them

by the word, i wrote gcode parser lib in python. it can be used as for parsing, as for drawing, as for checking. and it's easy to use (i hope) and much easier to customise comparint with handwriting parsers.

https://github.com/bhgv/python_gcode_parser_library

docs in README.md file example in example.py file

chamnit commented 8 years ago

@bhgv : Please keep in mind that gcode is not universally the same. Meaning there are slight variations between manufacturers and controllers. Grbl uses LinuxCNCs gcode descriptions. As far as an F0 error, this value is undefined. You cannot move at zero feed. I don't explicitly remember where LinuxCNCs gcode descriptions say this is an error but I can't see how it wouldn't be. If you can point where it says that F0 is valid, please let me know and I'll update Grbls parser.

vlachoudis commented 8 years ago

@bhgv bCNC is handling the gcode parameters in the following way

it is done in this way to be easier to use the python interpreter, and all his capabilities rather than providing something very restrictive

So your example has to written as

%feed_variable=300.0
..
F[feed_variable]
vlachoudis commented 8 years ago

I forgot to mention that there is a variable in the CNC class called stdexpr that is set to False normally if you change to True it should accept the "standard" gcode expressions with #num etc.. However I haven't test it, and I never put a user setting in the config

bhgv commented 8 years ago

hi today,

sorry for reverse order. @vlachoudis

=

are collected well by default and stored in GCode.vars, but unccessible in class CNC (post https://github.com/vlachoudis/bCNC/issues/334#issuecomment-221441168).

next, take a look to the post https://github.com/vlachoudis/bCNC/issues/334#issuecomment-221455655 examples from last git

(collected values stored in GCode.vars, look by yourself) maybe i didn't find all the moments where it place values, but after changes from https://github.com/vlachoudis/bCNC/issues/334#issuecomment-221455655 it began to fix parameters.

gEDA output file used to testing: link you may test by yourself. parameters are collected, but never fixed.

(in this file too many # parameters and i want to do their fixing automatically. the more that isn't so difficult)

i not propose to install my changes to bCNC, but if you help me to find links and usings of internal gcode parser, in future, maybe, it will be easier to configure, customise and extend.

@chamnit about F0 error. hmm, i looked to gcode more precise. it contains many strings like "G1 Z#101 F#102". it seems like it fixes only first parameter. i understood from where it takes F0.

about many variations of g-codes. take a look to proposed gcode parser. the main part for customising is

parser.set_callback_dict(            # set callback-foos for executing different g-codes and situations
  {
    "G0": G0_callback,               # foo(key, param) should return executed g-code as string
    "G1": G1_callback,               # foo(key, param) --//--
    "G2": G2_callback,               # foo(key, param) --//--
    # ...etc

    "default": G_def_cb,             # foo(key, param) default g-codes callback

    "set_param": set_param_callback, # foo(key, value)
    "get_param": get_param_callback, # foo(key) must return value or None

    "eol": New_line_callback,        # foo()

    "non_gcode_cmd":

    "no_callback": no_callback_callback, # foo(key, param, (line, row))

    "self": self_or_None             # self value used to call callbacks
                                     # if self_or_None is not defined or None
                                     # callbacks call as foo(key_params)
                                     # else if self_or_None defined and not None
                                     # callbacks call as foo(self_or_None, key_params)
  }
)

you can see that you only send callbacks-executors to different g-codes and situations before pasing. it's easiest and fastest way as for me. you can draw by them, you can send commands to stepper etc. without walking in-depth of parser.

changing the parser, adding/removing etc. this is the main part of parser:

PRODUCTIONS
  GCode =                                    (. 
                                                if self.gcode_test:
                                                   self._int_init() 
                                             .)
        {
            ParamDecl
          |
            NonGcodeCmd
          |
            { GcodeCmd }
            eolTok                           (. self.call("eol") .)
        }
        EOF                                  (. 
                                                if self.gcode_test:
                                                   self.print_gcode_out() 
                                             .)
        .

  ParamDecl = 
        param                                (. key = self.token.val .)
        "="
        Number<out num>                      (. self.set_param(key, num) .)
        eolTok
        .

  NonGcodeCmd =
        nonGcodeCmdBody                      (. self.call("non_gcode_cmd", self.token.val[1:]) .)
        eolTok
        .

  GcodeCmd =                                 (.
                                                cmd = ""
                                                num = ""
                                             .)
        (
            CmdNoMoveAloneLetter<out cmdLetter>    (. cmd = cmdLetter .)
            [
              Number<out num>                (. cmd += num .)
            ]                                (. self.call(cmd) .)
          |
            CmdNoMoveParamLetter<out cmdLetter>    (. cmd = cmdLetter .)
              Number<out num>                (. self.call(cmd, num) .)
          |
            CmdMoveLetter<out cmdLetter>     (. cmd = cmdLetter .)
            Number<out num>                  (. self.call(cmd, num) .)
        )
        .

  CmdNoMoveAloneLetter<out cmdLetter> =      (. cmdLetter = "" .)
        (
            "G"
          | "M"
          | "T"
        )                                    (. cmdLetter = self.token.val.upper() .)
        .

  CmdNoMoveParamLetter<out cmdLetter> =      (. cmdLetter = "" .)
        (
            "S"
          | "F"
          | "P"
          | "D"
          | "E"
          | "H"
          | "L"
          | "N"
          | "O"
        )                                    (. cmdLetter = self.token.val.upper() .)
        .

  CmdMoveLetter<out cmdLetter> =            (. cmdLetter = "" .)
        (
            "X"
          | "Y"
          | "Z"

          | "A"
          | "B"
          | "C"

          | "U"
          | "V"
          | "W"

          | "I"
          | "J"
          | "K"
          | "R"
        )                                    (. cmdLetter = self.token.val.upper() .)
        .

  Number<out num> =
          number                             (. num =  self.token.val .)
        | param                              (. num = self.get_param(self.token.val) .)
        .
bhgv commented 8 years ago

() - is () in regexp {} - is ()* in regexp [] - is ()? in regexp | - is | in regexp (. .) - is the code string(s) tailed to current regexp case <out val1, val2, val3 > - is parameters. val1 is output, val2, val3 are input

i think you can understand and begin to customise this parser just now, without reading the full documentation

bhgv commented 8 years ago

for example how to add [parameters] handling? it needs only add 1 string into the Number block:

 Number<out num> =
          number                             (. num =  self.token.val .)
        | param                              (. num = self.get_param(self.token.val) .)
    // -VV this one -
        | "[" number                         (. num = self.get_param("#" + self.token.val) .)
          "]"
        .

that's all

bhgv commented 8 years ago

i only ask you to help me with this experiment. as it so hard to receive all the information only from debugger. in the case of success bcnc become quicker as regexps are too slow, more changeable (for example: to add support of other cnc/3d printers or to drive steppers/heaters/directly by parallel gpio, etc) it may be possible to support different gcode choises by storing/changing different parsers as configs without changing main bcnc code

i only ask you to help me to find links of bcnc gcode parser to other bcnc code as it's your code and you know it much better than me.

sorry for my persistence, but bcnc is a very wonderful product and i want to deveop it for my purposes. and add support for some other controllers except grbl.

vlachoudis commented 8 years ago

@bhgv sorry but I didn't understand what you need?

bhgv commented 8 years ago

i've replaced the parser and now parameters handled well. both types

but needs more tests and move some parts of the parser to other file(s) for more easy development in the future

bhgv commented 8 years ago

i've replaced parser. in start part, draw part and send-to-controller part. if anyone want to test - welcome.

https://github.com/bhgv/bCNC

HomineLudens commented 8 years ago

Hi @bhgv. There's really a lot of stuff here :) Do you make some performance test using coco parse? How faster is respect a the original code?

bhgv commented 8 years ago

it's slower. last version (not from github) is 2-2.5 times slower than original. but i want to receive correct work now. now it draws correctly small gcodes, but big ones (> 1mb) it shows in the edit list but draws only part of code. i will upload after repairing

bhgv commented 8 years ago

new version was uploaded to github.

#12=34
% tt = 2*(3+4.5)
g0 x#12 y [ tt]

(speed of parsing isn't a big problem as it may be easily rewriten to C or C++. as coco support many of languages)


one thing in original bCNC: CNCCanvas.py -> class CNCCanvas: -> def drawPaths() strings:

                        if time.time() - startTime > DRAW_TIME:
                            raise AlarmException()

DRAW_TIME should be bigger or to remove this strings as they doesn't allow to draw big g-codes

HomineLudens commented 8 years ago

DRAW_TIME can be changed by the small combobox on the right top corner of Canvas. This allow bCNC to be run even in low end hardware by dropping the redraw method if it takes too much time.

image

bhgv commented 8 years ago

i've rewritten the parser on the C++ fork of coco. it ~2 times faster now than original. supported both types of parameters, mathematics in parameters, variables. ex

#12=34
% tt = 2 * 3 
g2 x[tt]  y#12
% ttt = (1-2*(1-3)+1)*3^(-2+(8*3)^-4)
g0 x#12  ( Ber_gt: fgh 4 f 8.  ) y [ ttt]
% tt = ttt + 2*(#12 +8)  / 3^(1/2)
g8 y 45 x32.8 z -[tt] z + [ttt]

after adding callback connectors like in python one i will upload it to test

vlachoudis commented 8 years ago

@bhgv it is nice what you are doing, however the whole idea having it in python was to profit from the python extensions, not only to simply evaluate an expression but to be able to embed a python program as a generator of gcode. Also another benefit in pure python is that it wont require any re-compilation therefore it can ran on any platform (windows, linux, mac etc...)

bhgv commented 8 years ago

@vlachoudis python and C++ gcode coco pasers have very same look and very same interface. and each can be used instead other. only the little change in the configs (on/off). C++ one is written as a regular python extention, it's just a faster clone of python one. without changing of other bCNC code and you can use/program bCNC as you are used to.




i think the best is - to upload & show. maybe tomorrow.

bhgv commented 8 years ago

bCNC with both Py and C++ parsers was uploaded. now drawing take more time than parsing (C++).