-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathplot.py
More file actions
142 lines (118 loc) · 4.25 KB
/
Copy pathplot.py
File metadata and controls
142 lines (118 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#!/usr/bin/env python3
"""Convert SVG pixel-unit G-code to mm and stream to GRBL plotter."""
import sys
import signal
import serial
import time
import threading
_enter_event = threading.Event()
# On Windows, editor.py's Stop button sends CTRL_BREAK_EVENT (plain SIGINT
# isn't supported by Popen.send_signal() there). Python has no default
# handler turning that into KeyboardInterrupt, so register one explicitly —
# it feeds the same `except KeyboardInterrupt` cleanup below.
def _raise_keyboard_interrupt(signum, frame):
raise KeyboardInterrupt
if sys.platform == 'win32':
signal.signal(signal.SIGBREAK, _raise_keyboard_interrupt)
def _kb_watcher():
"""Signal _enter_event on each Enter key press."""
while True:
try:
sys.stdin.readline()
except Exception:
return
_enter_event.set()
PORT = '/dev/ttyACM0'
BAUD = 115200
def pen_up(s):
"""Raise pen, wait for confirmation."""
s.write(b'\x18') # soft-reset to clear GRBL queue
time.sleep(1.5)
s.write(b'$X\n') # unlock
time.sleep(0.3)
s.reset_input_buffer()
s.write(b'G1 F16000 Z2\n')
s.write(b'G53 G0 X0 Y0\n')
s.readline()
def send_gcode(gcode_lines):
s = serial.Serial(PORT, BAUD, timeout=5)
time.sleep(2)
s.reset_input_buffer()
# Soft-reset and unlock
s.write(b'\x18')
time.sleep(1.5)
s.write(b'$X\n')
time.sleep(0.3)
s.reset_input_buffer()
# GRBL serial buffer = 128 bytes. Keep it full for smooth motion.
GRBL_BUF = 127
buf_used = 0
sent = 0
confirmed = 0
total = len(gcode_lines)
queue = [] # (bytes_in_cmd,)
threading.Thread(target=_kb_watcher, daemon=True).start()
print('Press Enter to pause / resume.', flush=True)
last_pct = -1
try:
while confirmed < total:
# Feed as many lines as fit in GRBL buffer
while sent < total:
cmd = (gcode_lines[sent] + '\n').encode()
if buf_used + len(cmd) > GRBL_BUF:
break
s.write(cmd)
queue.append(len(cmd))
buf_used += len(cmd)
sent += 1
# Wait for next 'ok'
resp = s.readline().decode(errors='replace').strip()
if resp == 'ok' or resp.startswith('error'):
buf_used -= queue.pop(0)
confirmed += 1
pct = confirmed * 100 // total // 10 * 10
if pct != last_pct:
print(f' {pct}%', flush=True)
last_pct = pct
# Pause/resume on Enter
if _enter_event.is_set():
_enter_event.clear()
s.write(b'!') # feed hold — decelerate to stop
time.sleep(0.3)
s.write(b'\x18') # soft-reset — clear GRBL queue
time.sleep(1.5)
s.write(b'$X\n') # unlock
time.sleep(0.3)
s.reset_input_buffer()
s.write(b'G1 F16000 Z2\n') # raise pen
s.readline()
# Rewind buffer tracking to last confirmed command
queue.clear()
buf_used = 0
sent = confirmed
print(f'\nПауза на {confirmed / total * 100:.0f}%. Натисніть Enter щоб продовжити...', flush=True)
_enter_event.wait() # block until second Enter
_enter_event.clear()
s.write(b'$X\n') # unlock after adjustments
time.sleep(0.3)
s.reset_input_buffer()
print('Продовжую...', flush=True)
except KeyboardInterrupt:
print('\nInterrupted — raising pen...', flush=True)
pen_up(s)
s.close()
sys.exit(1)
# Pen up and go home
for cmd in ['G1 F16000 Z2\n', 'G53 G0 X0 Y0\n']:
s.write(cmd.encode())
s.readline()
s.close()
print('Done.')
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f'Usage: python3 plot.py <file.gcode>')
sys.exit(1)
with open(sys.argv[1]) as f:
lines = [l.strip() for l in f if l.strip()]
print(f'Streaming {len(lines)} lines to {PORT} ...')
send_gcode(lines)