diff --git a/README.md b/README.md new file mode 100644 index 0000000..a29dd1d --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# pygcode + +GCODE Parser for Python + +Currently in development, `pygcode` is a low-level GCode interpreter +for python. + + +# Installation + +``` + git clone git@github.com:petaflot/pygcode.git + cd pygcode + pip install -e . +``` + + +# Documentation + +[Check out the wiki](https://github.com/fragmuffin/pygcode/wiki) for documentation. diff --git a/README.rst b/README.rst deleted file mode 100644 index dd94b32..0000000 --- a/README.rst +++ /dev/null @@ -1,24 +0,0 @@ -======= -pygcode -======= - -GCODE Parser for Python - -Currently in development, ``pygcode`` is a low-level GCode interpreter -for python. - - -Installation -============ - -Install using ``pip`` - -``pip install pygcode`` - -or `download directly from PyPi `__ - - -Documentation -============= - -`Check out the wiki `__ for documentation. diff --git a/src/pygcode/__init__.py b/src/pygcode/__init__.py index 27611cb..08b0fb7 100644 --- a/src/pygcode/__init__.py +++ b/src/pygcode/__init__.py @@ -54,7 +54,7 @@ __all__ = [ 'GCodeArcMoveCW', 'GCodeBoringCycleDwellFeedOut', 'GCodeBoringCycleFeedOut', - 'GCodeCancelCannedCycle', + #'GCodeCancelCannedCycle', 'GCodeCancelToolLengthOffset', 'GCodeCannedCycle', 'GCodeCannedCycleReturnPrevLevel', @@ -336,7 +336,7 @@ from .gcodes import ( GCodeArcMoveCW, GCodeBoringCycleDwellFeedOut, GCodeBoringCycleFeedOut, - GCodeCancelCannedCycle, + #GCodeCancelCannedCycle, GCodeCancelToolLengthOffset, GCodeCannedCycle, GCodeCannedCycleReturnPrevLevel, diff --git a/src/pygcode/dialects/__init__.py b/src/pygcode/dialects/__init__.py index 9a079a3..43edacb 100644 --- a/src/pygcode/dialects/__init__.py +++ b/src/pygcode/dialects/__init__.py @@ -8,6 +8,7 @@ __all__ = [ # dialects 'linuxcnc', 'reprap', + 'prusa', ] @@ -18,9 +19,10 @@ from .mapping import word_dialect # Dialects from . import linuxcnc from . import reprap +from . import prusa -_DEFAULT = 'linuxcnc' +_DEFAULT = 'prusa' def get_default(): diff --git a/src/pygcode/dialects/prusa.py b/src/pygcode/dialects/prusa.py new file mode 100644 index 0000000..1d412dd --- /dev/null +++ b/src/pygcode/dialects/prusa.py @@ -0,0 +1,202 @@ +import re + +from .utils import WordType + +# ======================== WORDS ======================== + +REGEX_FLOAT = re.compile(r'^\s*-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float +REGEX_INT = re.compile(r'^\s*-?\d+') +REGEX_POSITIVEINT = re.compile(r'^\s*\d+') +REGEX_CODE = re.compile(r'^\s*\d+(\.\d)?') # float, but can't be negative + +# Value cleaning functions +def _clean_codestr(value): + if value < 10: + return "0%g" % value + return "%g" % value + +CLEAN_NONE = lambda v: v +CLEAN_FLOAT = lambda v: "{0:g}".format(round(v, 3)) +CLEAN_CODE = _clean_codestr +CLEAN_INT = lambda v: "%g" % v + +WORD_MAP = { + # Descriptions copied from wikipedia: + # https://en.wikipedia.org/wiki/G-code#Letter_addresses + + # Rotational Axes + 'A': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Absolute or incremental position of A axis (rotational axis around X axis)", + clean_value=CLEAN_FLOAT, + ), + 'B': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Absolute or incremental position of B axis (rotational axis around Y axis)", + clean_value=CLEAN_FLOAT, + ), + 'C': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Absolute or incremental position of C axis (rotational axis around Z axis)", + clean_value=CLEAN_FLOAT, + ), + 'D': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines diameter or radial offset used for cutter compensation. D is used for depth of cut on lathes. It is used for aperture selection and commands on photoplotters.", + clean_value=CLEAN_FLOAT, + ), + # Feed Rates + 'E': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Precision feedrate for threading on lathes", + clean_value=CLEAN_FLOAT, + ), + 'F': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Feedrate", + clean_value=CLEAN_FLOAT, + ), + # G-Codes + 'G': WordType( + cls=float, + value_regex=REGEX_CODE, + description="Address for preparatory commands", + clean_value=CLEAN_CODE, + ), + # Tool Offsets + 'H': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines tool length offset; Incremental axis corresponding to C axis (e.g., on a turn-mill)", + clean_value=CLEAN_FLOAT, + ), + # Arc radius center coords + 'I': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines arc center in X axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles.", + clean_value=CLEAN_FLOAT, + ), + 'J': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines arc center in Y axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles.", + clean_value=CLEAN_FLOAT, + ), + 'K': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines arc center in Z axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles, equal to L address.", + clean_value=CLEAN_FLOAT, + ), + # Loop Count + 'L': WordType( + cls=int, + value_regex=REGEX_POSITIVEINT, + description="Fixed cycle loop count; Specification of what register to edit using G10", + clean_value=CLEAN_INT, + ), + # Miscellaneous Function + 'M': WordType( + cls=float, + value_regex=REGEX_CODE, + description="Miscellaneous function", + clean_value=CLEAN_CODE, + ), + # Line Number + 'N': WordType( + cls=int, + value_regex=REGEX_POSITIVEINT, + description="Line (block) number in program; System parameter number to change using G10", + clean_value=CLEAN_INT, + ), + # Program Name + 'O': WordType( + cls=str, + value_regex=re.compile(r'^.+$'), # all the way to the end + description="Program name", + clean_value=CLEAN_NONE, + ), + # Parameter (arbitrary parameter) + 'P': WordType( + cls=str, # parameter is often an integer, but can be a float + value_regex=re.compile(r'^.+$'), # all the way to the end + description="Checks the parameters of the printer and gcode and performs compatibility check", + clean_value=CLEAN_NONE, + ), + # Peck increment + 'Q': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Depth to increase on each peck; Peck increment in canned cycles", + clean_value=CLEAN_FLOAT, + ), + # Arc Radius + 'R': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines size of arc radius, or defines retract height in milling canned cycles", + clean_value=CLEAN_FLOAT, + ), + # Spindle speed + 'S': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Defines speed, either spindle speed or surface speed depending on mode", + clean_value=CLEAN_FLOAT, + ), + # Tool Selecton + 'T': WordType( + cls=str, + value_regex=REGEX_POSITIVEINT, # tool string may have leading '0's, but is effectively an index (integer) + description="Tool selection", + clean_value=CLEAN_NONE, + ), + # Incremental axes + 'U': WordType( + cls=str, + value_regex=re.compile(r'^.*$'), # all the way to the end + description="todo", + clean_value=CLEAN_NONE, + ), + 'V': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Incremental axis corresponding to Y axis", + clean_value=CLEAN_FLOAT, + ), + 'W': WordType( + cls=str, + value_regex=re.compile(r'^.*$'), # all the way to the end + description="When used with G28 just specifies without bed leveling, when used with M900 specifies width", + clean_value=CLEAN_NONE, + ), + # Linear Axes + 'X': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Absolute or incremental position of X axis.", + clean_value=CLEAN_FLOAT, + ), + 'Y': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Absolute or incremental position of Y axis.", + clean_value=CLEAN_FLOAT, + ), + 'Z': WordType( + cls=float, + value_regex=REGEX_FLOAT, + description="Absolute or incremental position of Z axis.", + clean_value=CLEAN_FLOAT, + ), +} + + +# ======================== G-CODES ======================== diff --git a/src/pygcode/gcodes.py b/src/pygcode/gcodes.py index fead76d..f396494 100644 --- a/src/pygcode/gcodes.py +++ b/src/pygcode/gcodes.py @@ -32,7 +32,7 @@ from .exceptions import GCodeParameterError, GCodeWordStrError # # Modal Groups: # Only one mode of each modal group can be active. That is to say, a -# modal g-code can only change the sate of a previously set mode if +# modal g-code can only change the state of a previously set mode if # they're in the same group. # For example: # G20 (mm), and G21 (inches) are in group 6 @@ -213,6 +213,14 @@ class GCode(object): word_str=word_str, parameters=param_str, ) + + def __hash__(self): + """Hash representation of the gcode, for set and dictionary usage""" + try: + return hash(self.word_key) + except TypeError: + return hash(self.word_letter) # May also want to retrieve additional value info + def _default_word(self): if self.default_word: @@ -511,8 +519,6 @@ class GCodeRigidTapping(GCodeMotion): word_key = Word('G', 33.1) - - # ======================= Canned Cycles ======================= # (X Y Z or U V W apply to canned cycles, depending on active plane) # CODE PARAMETERS DESCRIPTION @@ -1267,7 +1273,7 @@ class GCodeAnalogOutputImmediate(GCodeAnalogOutput): # G28, G28.1 Go/Set Predefined Position # G30, G30.1 Go/Set Predefined Position # G53 Move in Machine Coordinates -# G92 Coordinate System Offset +# G92 ABCXYZUVW Coordinate System Offset # G92.1, G92.2 Reset G92 Offsets # G92.3 Restore G92 Offsets # M101 - M199 P Q User Defined Commands @@ -1301,6 +1307,7 @@ class GCodeSet(GCodeNonModal): class GCodeGotoPredefinedPosition(GCodeNonModal): """G28,G30: Goto Predefined Position (rapid movement)""" + param_letters = set('W') @classmethod def word_matches(cls, w): return (w.letter == 'G') and (w.value in [28, 30]) @@ -1310,6 +1317,7 @@ class GCodeGotoPredefinedPosition(GCodeNonModal): class GCodeSetPredefinedPosition(GCodeNonModal): """G28.1,G30.1: Set Predefined Position""" # redundancy in language there, but I'll let it slide + param_letters = set('W') @classmethod def word_matches(cls, w): return (w.letter == 'G') and (w.value in [28.1, 30.1]) @@ -1325,6 +1333,7 @@ class GCodeMoveInMachineCoords(GCodeNonModal): class GCodeCoordSystemOffset(GCodeNonModal): """G92: Coordinate System Offset""" + param_letters = set('XYZABCUVW') word_key = Word('G', 92) exec_order = 230 @@ -1358,6 +1367,115 @@ class GCodeUserDefined(GCodeNonModal): exec_order = 130 modal_group = MODAL_GROUP_MAP['user_defined'] +# ======================= Prusa ======================= +# CODE PARAMETERS DESCRIPTION +# M862.1 P Q Nozzle Diameter +# M862.2 P Q Model Code +# M862.3 P Q Model Name +# M862.4 P Q Firmware Version +# M862.5 P Q GCode Level +# M115 V U Firmware info +# M73 P R Q S C D Set/Get print progress +# M205 S T B X Y Z E Set advanced settings +# M104 S Set extruder temperature +# M109 B R S Wait for extruder temperature +# M140 S Set bed temperature +# M190 R S Wait for bed temperature +# M204 S T Acceleration settings +# M221 S T Set extrude factor override percentage +# M106 S Set fan speed +# G80 N R V L R F B Mesh-based Z probe + +class GCodePrintChecking(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('PQ') + +class GCodeNozzleDiameterPrintChecking(GCodePrintChecking): + """M862.1: Nozzle Diameter""" + word_key = Word('M', 862.1) + +class GCodeModelCodePrintChecking(GCodePrintChecking): + """M862.2: Model Code""" + word_key = Word('M', 862.2) + +class GCodeModelNamePrintChecking(GCodePrintChecking): + """M862.3: Model Name""" + word_key = Word('M', 862.3) + +class GCodeFirmwareVersionPrintChecking(GCodePrintChecking): + """M862.4: Firmware Version""" + word_key = Word('M', 862.4) + +class GCodeGcodeLevelPrintChecking(GCodePrintChecking): + """M862.5: Gcode Level""" + word_key = Word('M', 862.5) + +class GCodeFirmwareInfo(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('VU') + word_key = Word('M', 115) + +class GCodePrintProgress(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('PRQSCD') + word_key = Word('M', 73) + +class GCodeSetAdvancedSettings(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('STBXYZE') + word_key = Word('M', 205) + +class GCodeSetExtruderTemperature(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('S') + word_key = Word('M', 104) + +class GCodeWaitForExtruderTemperature(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('BRS') + word_key = Word('M', 109) + +class GCodeSetBedTemperature(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('S') + word_key = Word('M', 140) + +class GCodeWaitForBedTemperature(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('RS') + word_key = Word('M', 190) + +class GCodeAccelerationSettings(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('ST') + word_key = Word('M', 204) + +class GCodeSetExtrudeFactorOverridePercentage(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('ST') + word_key = Word('M', 221) + +class GCodeSetFanSpeed(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('S') + word_key = Word('M', 106) + +class GCodeMeshBasedZProbe(GCode): + exec_order = 999 + modal_group = MODAL_GROUP_MAP['user_defined'] + param_letters = set('NRVLRFB') + word_key = Word('G', 80) # ======================= Utilities =======================