AuxPreprocessor: canonical M100-M103 for ATC pneumatics
Map four user-defined M-codes to the existing ATC hooks:
M100 DROPTOOL -> (MSG,HOOK:droptool:)
M101 GRABTOOL -> (MSG,HOOK:grabtool:)
M102 RELEASE -> (MSG,HOOK:release:)
M103 CLAMP -> (MSG,HOOK🗜️)
M100-M103 are in LinuxCNC/Buildbotics user-defined range so the
planner won't error on the raw codes if the preprocessor is bypassed.
Stripped from the residual line and replaced with the hook line.
Order is left-to-right; multiple ATC codes per line and ATC+W on
the same line both work (M100 W10 -> drop then move to W=10).
The file scanner (file_uses_aux, formerly file_uses_w) now wakes
up for either W tokens or ATC M-codes; backwards-compat alias kept.
MDI rewrite (Mach._rewrite_w_mdi) updated likewise.
Tested locally with mixed ATC/W gcode in tmp/20260501_atc_mcodes.
This commit is contained in:
@@ -42,6 +42,30 @@ _PAREN_COMMENT_RE = re.compile(r'\([^)]*\)')
|
|||||||
# Modal G-code groups we care about.
|
# Modal G-code groups we care about.
|
||||||
_MODAL_RE = re.compile(r'(?<![A-Za-z_0-9])[Gg]\s*0*(\d+(?:\.\d+)?)')
|
_MODAL_RE = re.compile(r'(?<![A-Za-z_0-9])[Gg]\s*0*(\d+(?:\.\d+)?)')
|
||||||
|
|
||||||
|
# ATC pneumatics. We map a small range of user-defined M-codes onto
|
||||||
|
# our existing HOOK: events. M100-M103 are in LinuxCNC/Buildbotics'
|
||||||
|
# user-defined range so the planner won't error on them - but it also
|
||||||
|
# won't *do* anything with them, so we strip them out and emit the
|
||||||
|
# matching hook line in their place. M6 is intentionally NOT mapped:
|
||||||
|
# users keep their own probe-and-prompt M6 override.
|
||||||
|
#
|
||||||
|
# M100 DROPTOOL (eject current tool, automatic sequence)
|
||||||
|
# M101 GRABTOOL (auto-clamp on inserted holder)
|
||||||
|
# M102 RELEASE (manually open collet, no clamp)
|
||||||
|
# M103 CLAMP (manually close collet with bleed)
|
||||||
|
_ATC_M_CODES = {
|
||||||
|
100: 'droptool',
|
||||||
|
101: 'grabtool',
|
||||||
|
102: 'release',
|
||||||
|
103: 'clamp',
|
||||||
|
}
|
||||||
|
# A token like 'M100' or 'm103', not preceded/followed by alnum.
|
||||||
|
_ATC_M_RE = re.compile(
|
||||||
|
r'(?<![A-Za-z_0-9])[Mm]\s*0*(' +
|
||||||
|
'|'.join(str(n) for n in _ATC_M_CODES) +
|
||||||
|
r')(?![\w.])'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuxPreprocessorError(Exception):
|
class AuxPreprocessorError(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -65,9 +89,10 @@ class AuxPreprocessor(object):
|
|||||||
# ------------------------------------------------------------------ scan
|
# ------------------------------------------------------------------ scan
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def file_uses_w(path):
|
def file_uses_aux(path):
|
||||||
"""Quick check: does this file contain any W-axis word? Used to skip
|
"""Quick check: does this file contain anything the preprocessor
|
||||||
preprocessing entirely for files that don't care about W."""
|
would rewrite (W axis tokens or ATC M-codes)? Used to skip
|
||||||
|
preprocessing entirely for files that don't need it."""
|
||||||
try:
|
try:
|
||||||
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
with open(path, 'r', encoding='utf-8', errors='replace') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -75,10 +100,16 @@ class AuxPreprocessor(object):
|
|||||||
code = code.split(';', 1)[0]
|
code = code.split(';', 1)[0]
|
||||||
if _W_TOKEN_RE.search(code):
|
if _W_TOKEN_RE.search(code):
|
||||||
return True
|
return True
|
||||||
|
if _ATC_M_RE.search(code):
|
||||||
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Backwards-compat alias: external callers used file_uses_w before
|
||||||
|
# the ATC M-codes were added.
|
||||||
|
file_uses_w = file_uses_aux
|
||||||
|
|
||||||
# ------------------------------------------------------------------ core
|
# ------------------------------------------------------------------ core
|
||||||
|
|
||||||
def _strip_w(self, line):
|
def _strip_w(self, line):
|
||||||
@@ -139,8 +170,38 @@ class AuxPreprocessor(object):
|
|||||||
# vs incremental matches what the planner sees for XYZ).
|
# vs incremental matches what the planner sees for XYZ).
|
||||||
self._detect_modals(code, modal)
|
self._detect_modals(code, modal)
|
||||||
|
|
||||||
|
# ATC M-codes (M100-M103). Each ATC M-code on the line
|
||||||
|
# is replaced with its (MSG,HOOK:<event>:) line and
|
||||||
|
# stripped from the residual. Multiple ATC codes per
|
||||||
|
# line are honoured but order is left-to-right. We do
|
||||||
|
# this BEFORE the W-axis path so a line like
|
||||||
|
# "M100 W10" cleanly emits drop+move.
|
||||||
|
atc_matches = list(_ATC_M_RE.finditer(line))
|
||||||
|
if atc_matches:
|
||||||
|
rewrote_any = True
|
||||||
|
for m in atc_matches:
|
||||||
|
try: num = int(m.group(1))
|
||||||
|
except ValueError: continue
|
||||||
|
event = _ATC_M_CODES.get(num)
|
||||||
|
if event:
|
||||||
|
fout.write('(MSG,HOOK:%s:)\n' % event)
|
||||||
|
line = _ATC_M_RE.sub('', line)
|
||||||
|
code = _PAREN_COMMENT_RE.sub('', line)
|
||||||
|
code = code.split(';', 1)[0]
|
||||||
|
# If nothing else is left on the line, we're done.
|
||||||
|
if not code.strip():
|
||||||
|
# Preserve any trailing comment, but skip if the
|
||||||
|
# whole line is empty after the M-code strip.
|
||||||
|
rest = line.rstrip()
|
||||||
|
if rest:
|
||||||
|
fout.write(rest + '\n')
|
||||||
|
continue
|
||||||
|
|
||||||
if not _W_TOKEN_RE.search(code):
|
if not _W_TOKEN_RE.search(code):
|
||||||
fout.write(raw)
|
# No W work to do; emit whatever's left after ATC
|
||||||
|
# M-code stripping (or the original line if there
|
||||||
|
# were no ATC codes).
|
||||||
|
fout.write(line + '\n' if atc_matches else raw)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rewrote_any = True
|
rewrote_any = True
|
||||||
@@ -214,9 +275,10 @@ class AuxPreprocessor(object):
|
|||||||
|
|
||||||
|
|
||||||
def preprocess_file(src_path, log=None, w_first=True):
|
def preprocess_file(src_path, log=None, w_first=True):
|
||||||
"""Convenience: rewrite src_path in place if it uses W.
|
"""Convenience: rewrite src_path in place if it uses anything the
|
||||||
|
preprocessor handles (W axis tokens or ATC M-codes).
|
||||||
Returns True if the file was rewritten."""
|
Returns True if the file was rewritten."""
|
||||||
if not AuxPreprocessor.file_uses_w(src_path):
|
if not AuxPreprocessor.file_uses_aux(src_path):
|
||||||
return False
|
return False
|
||||||
pre = AuxPreprocessor(log=log, w_first=w_first)
|
pre = AuxPreprocessor(log=log, w_first=w_first)
|
||||||
fd, tmp = tempfile.mkstemp(prefix='auxpre_', suffix='.nc',
|
fd, tmp = tempfile.mkstemp(prefix='auxpre_', suffix='.nc',
|
||||||
|
|||||||
@@ -270,8 +270,9 @@ class Mach(Comm):
|
|||||||
"""Apply the W-axis preprocessor to a single MDI line. Returns
|
"""Apply the W-axis preprocessor to a single MDI line. Returns
|
||||||
possibly-multi-line G-code with HOOK: comments inserted."""
|
possibly-multi-line G-code with HOOK: comments inserted."""
|
||||||
try:
|
try:
|
||||||
from bbctrl.AuxPreprocessor import AuxPreprocessor, _W_TOKEN_RE
|
from bbctrl.AuxPreprocessor import (
|
||||||
if not _W_TOKEN_RE.search(cmd):
|
AuxPreprocessor, _W_TOKEN_RE, _ATC_M_RE)
|
||||||
|
if not _W_TOKEN_RE.search(cmd) and not _ATC_M_RE.search(cmd):
|
||||||
return cmd
|
return cmd
|
||||||
import io, tempfile, os
|
import io, tempfile, os
|
||||||
# AuxPreprocessor.process is file-based; route through
|
# AuxPreprocessor.process is file-based; route through
|
||||||
|
|||||||
Reference in New Issue
Block a user