Skip to content

A simple CNC state machine implemented in Python that can be used for simulation and processing of G-code

License

Notifications You must be signed in to change notification settings

michaelfranzl/gcode-machine

Repository files navigation

GcodeMachine

Unit tests on master branch

CNC controller state machine for parsing and pre-processing of G-code, implemented as a Python 3 class.

I split it off my project grbl-streamer to make it generally usable.

This machine is completely unit tested and was also successfully tested by realtime processing of G-code for my wood-milling CNC machine.

Features

After passing initial conditions (machine position, coordinate system offsets, current coordinate system) to the constructor, you can send G-code lines to the machine. Sent G-code lines change the state of the machine:

  • machine position, absolute to the machine (position_m)
  • working position, relative to current coordinate system (position_w)
  • coordinate system (current_cs)
  • feed rate (current_feed)
  • travel distances (the list dist_xyz, the scalar dist)
  • spindle speed (current_spindle_speed)
  • motion mode (current_motion_mode)
  • distance mode (current_distance_mode)
  • plane mode (current_plane_mode)
  • comment (comment)

To change the current coordinate system, use the accessor current_cs= (as this also recalculates the working position pos_w).

Optionally, the physical machine position can at any time be updated from the state of a real CNC controller; for this, the accessor position_m= should be used (as this recalculates the working position pos_w).

In addition, by calling corresponding methods, the machine also can pre-process the set G-code line:

  • variable substitution (#1, #2, etc. using the vars dict attribute)
  • spindle scaling (scale_spindle() using the spindle_factor attribute)
  • feed override (override_feed() using the request_feed attribute)
  • tidying (tidy()) (comments, change comment format from parentheses to semicolon, spaces, command allow-list)

The methods split_lines and fractionize are pure pre-processing methods that do not modify the state of the machine, but return a list of G-code. In a future release, these methods may be moved to class methods, or into a separate class:

  • fractionize() splits linear (G1) and arc (G2, G3) motions into tiny linear G1 motions
  • split_lines() splits several space-separated commands into a list of separate commands

Callers can assign a custom callback function to the attribute callback, which will be called when certain processing events happen. Currently, the only evens are:

  • on_feed_change: When the G-Code line changes the feed speed. The first argument is the feed speed as a floating point number.
  • on_var_undefined: When the processor encounters a variable that was not defined before. The first argument is the name of the undefined variable.

Last but not least, the attribute logger has a logger created by logging.getLogger('gcode_machine') that can be used by the application.

Installation

pip install gcode-machine

Requirements

  • The Python version specified in the file .python-version

Development

Dependencies are managed using pipenv:

pip install pipenv --user
pipenv install --dev

To run the tests:

pipenv run make test

Building

pipenv run make dist

Usage

from gcode_machine import GcodeMachine

# initial conditions
impos = (0, 0, 0) # initial machine position, default zero
ics = "G54" # initial coordinate system , default G54
cs_offsets = {"G54": (0, 0, 0), "G55": (10, 20, 30)} # coordinate system offsets

# make a new machine
gcm = GcodeMachine(impos, ics, cs_offsets)
input = ["G0 Z-10", "G1 X10 Y10"]
output = []

for line in input:
    gcm.set_line(line)       # feed the line into the machine

    gcm.strip()              # clean up whitespace
    gcm.tidy()               # filter commands by a whitelist
    gcm.find_vars()          # parse variable usages
    gcm.substitute_vars()    # substitute variables
    gcm.parse_state()        # parse the line and update the machine state
    gcm.override_feed()      # substitute F values in the current line

    # When done processing:
    output.append(gcm.line)  # read the processed line back from the machine
    gcm.done()               # update the machine position

Explanation of this example:

For each iteration of the loop, feed the command line into the machine with the method set_line(). Then, call individual processing methods as needed for your application; this gives a lot of flexibility to the application.

When done with one line, call done() -- this will update the virtual tool position.

Processing examples

Please also see the unit tests for the full feature set.

Linear fractionization of arcs

gcm.position_m = (0,0,0)
gcm.set_line("G2 X10 Y10 I5 J5")
gcm.parse_state()
print('\n'.join(gcm.fractionize()))

Result:

;_gcm.arc_begin[G2 X10 Y10 I5 J5]
;_gcm.color_begin[0.35,0.50,0.40]
G1X-0.33Y0.353Z0
X-0.634Y0.727
X-0.913Y1.122
...
X8.037Y11.386
X8.466Y11.164
X8.878Y10.913
X9.273Y10.634
X9.647Y10.33
X10Y10
;_gcm.color_end
;_gcm.arc_end

Comment transform

gcm.set_line("G0 X0 (bob) Y0 (alice)")
gcm.transform_comments()
print(gcm.line)

Result:

G0 X0  Y0 ;alice

Tidying and allow-listing

gcm.set_line("T2")
gcm.tidy()
print(gcm.line)

Result:

;T02 ;_gcm.unsupported

Splitting commands

gcm.set_line("G55 M3 T2")
gcm.split_lines()

Result:

['G55 ', 'M3 ', 'T2']

About

A simple CNC state machine implemented in Python that can be used for simulation and processing of G-code

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published