Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
bhgv opened this issue May 24, 2016 · 23 comments
Open

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

bhgv opened this issue May 24, 2016 · 23 comments

Comments

@bhgv
Copy link

bhgv commented May 24, 2016

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
Copy link
Author

bhgv commented May 24, 2016

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
Copy link
Author

bhgv commented May 24, 2016

..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
Copy link
Author

bhgv commented May 25, 2016

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

@bhgv
Copy link
Author

bhgv commented May 25, 2016

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
Copy link
Author

bhgv commented May 25, 2016

with changes from the post #334 (comment) 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
Copy link

chamnit commented May 25, 2016

@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
Copy link
Owner

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

  • like in python. All lines starting with % will be executed by python and the result if any will be send to grbl.
  • variables can be accessed if enclosed in square brackets []
  • all # variables are changed to _ so it can be understood and parsed by python
  • expressions are using normal parenthesis and not square brackets e.g. sin(angle)

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
Copy link
Owner

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
Copy link
Author

bhgv commented May 25, 2016

hi today,

sorry for reverse order.
@vlachoudis
#=
are collected well by default and stored in GCode.vars, but unccessible in class CNC (post #334 (comment)).

next, take a look to the post #334 (comment)
examples from last git

  • CNC.vars["feed"] = float(g[1:])
  • value = float(cmd[1:])

(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 #334 (comment) 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
Copy link
Author

bhgv commented May 25, 2016

() - 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
Copy link
Author

bhgv commented May 25, 2016

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
Copy link
Author

bhgv commented May 25, 2016

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
Copy link
Owner

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

@bhgv
Copy link
Author

bhgv commented May 29, 2016

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
Copy link
Author

bhgv commented May 29, 2016

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

  • the parser itself - GCode_parser folder
  • GCode_parser/Gparser.py - class with all callbacks, utilites, etc.
  • GCode_parser/Parser_engine/GCode.atg - gcode parser declaration

@HomineLudens
Copy link
Contributor

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
Copy link
Author

bhgv commented May 30, 2016

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
Copy link
Author

bhgv commented May 31, 2016

new version was uploaded to github.

  • ~2.5 times slower than original. it was tested on 1.5mb code file.
  • handling of both choises of parameters works good. ex:
#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
Copy link
Contributor

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
Copy link
Author

bhgv commented Jun 3, 2016

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
Copy link
Owner

@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
Copy link
Author

bhgv commented Jun 3, 2016

@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.


  • python one is fast with not big gcodes or on fast computers (only ~2 times slower than original parser).
  • C++ one may be used for slow platforms (as raspberry pi or old computers) and hevy-weight gcodes.
  • you can swithch between them easily in any time (original parser parses the whole gcode file on every redrawing/moving/editing). you can not use C++ parser at all.
  • coco C++ parser is highly platform independed and in the future it can be rewritten to C to better portability. this one doesn't use C++ extentions. i prefered C++ just only for faster rewriting.

  • what's the benefits of non-handwrited coco parser? - it may be extended or changed to parse other vector formats (svg, eps, gerber etc) in the most easy way.
  • bCNC use the same parser for starting reading, filling the edit list, drawing. by changing only the parser it can be used with many different formats of files or add new features (like #parameters). and it can be debugged separatelly.
  • it's possible to have many parsers for different formats and use the corresponding parser just for input file format.

  • what's the benefits of using raspbery like slow boards? - they can be installed directly on a cnc as powerful cnc/3d printer controler with a big screen and rich interface, file viewers/converters/editors and other good things. with connection to a big computer by lan or wifi. and they cost today from only $10.

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

@bhgv
Copy link
Author

bhgv commented Jun 4, 2016

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

  • to switch between them - go to ./conf/parser.py and set py_parser False or True
  • to compile C++ parser to other platform (not a linux-armv7) - go to ./GCode_parser/Parser_engine/c/tools/coco_cpp and compile Coco itself. there have scripts to compile on windows and *nixes. after, go to ./GCode_parser/Parser_engine/c and build the parser - bld.sh.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants