-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcli.py
executable file
·278 lines (261 loc) · 9.67 KB
/
cli.py
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/usr/bin/env python
from __future__ import division
import math
import numpy
import argparse
try:
import Image
except ImportError:
from PIL import Image
from itertools import groupby
from pprint import pprint
version = "0.7"
def loadArray(arr):
im = Image.fromarray(arr)
def loadImage(file):
#Open Source
img = Image.open(file)
#aspect
height = args.width/(img.size[0]/img.size[1])
#This resize is close
#Rotated and flipped to look proper
resize = (
int(math.floor(args.density * args.width)),
int(math.floor(args.density * height))
)
#TODO: make quantize work with a palette, better.
if args.palette:
if args.shades < 3:
palette = [
0, 0, 0,
255, 255, 255,
] + [255, ] * 254 * 3
else:
palette = [0, 0, 0,]
steps = int(math.floor(256/(args.shades-1)))
for c in range(args.shades-2):
m = c+1
palette = palette + [steps*m,steps*m,steps*m]
palette = palette + [255, ] * (256-args.shades) * 3
pimage = Image.new("P", (1, 1), 0)
pimage.putpalette(palette)
if args.debug:
print "Number of shades:" + str(args.shades)
print "Number of steps:" + str(steps)
print palette
#.convert("L", palette=pimage) \
img.convert("L") \
.quantize(colors=args.shades, palette=pimage) \
.resize(resize, Image.LANCZOS) \
.transpose(Image.ROTATE_180) \
.transpose(Image.FLIP_LEFT_RIGHT) \
.save(args.output+".gcode.jpg")
#.show()
else:
if args.debug:
print "No palette"
img.convert("L") \
.resize(resize, Image.LANCZOS) \
.transpose(Image.ROTATE_180) \
.transpose(Image.FLIP_LEFT_RIGHT) \
.save(args.output+".gcode.jpg")
#.show()
#Save temp, TODO: do without writing file? tired.
img = Image.open(args.output+".gcode.jpg")
#img.show()
imgarr = numpy.array(img)
return imgarr
def laserOff():
appendGcode(args.laseroff)
def laserOn(power):
appendGcode(args.modifier + str(power) + " " + args.laseron)
#creates a 255x20 black to white gradient for testing settings
def gradientTest():
test = Image.new( 'RGB', (255,20), "black") # create a new black image
pixels = test.load() # create the pixel map
for i in range(test.size[0]): # for every pixel:
for j in range(test.size[1]):
pixels[i,j] = (i, i, i) # set the colour accordingly
test.save("gradient_testpatten.jpg")
def appendGcode(line):
global lines
#Remove any duplicate commands...
#optimizing where possible.
numlines = len(lines)
prefix = line[:2]
if len(lines) > 0:
if line == lines[numlines-1]:
pass
#Duplicate gcode found. OMIT
#if args.debug:
# print "OMIT DUPLICATE:" + line
elif prefix == lines[numlines-1][:2] and "Y" not in lines[numlines-1]:
lines[numlines-1] = line
#if args.debug:
# print "OVERWRITE:" + line
elif args.laseron in line and args.laseroff in lines[numlines-1]:
lines[numlines-1] = line
#if args.debug:
# print "Keep laser on:" + line
else:
lines.append(line)
else:
lines.append(line)
#Set laser mode for grbl 1.1
#Appears this should work? in practice?
def laserMode(status):
if status == 1:
appendGcode("$38=1")
else:
appendGcode("$38=0")
#TODO: config profiles so you don't have to mess around.....
#SERIOUSLY! ^^^^^^^
parser = argparse.ArgumentParser()
parser.add_argument('file', help='image file name')
parser.add_argument('width', type=int, help='Output width in MM (ish), FIX ME')
#Versioning
parser.add_argument('-v', '--version', action='version', version=version )
#TODO: arguments and profiles.....
#parser.add_argument('-s', '--size', type=float, help='pixelsize')
parser.add_argument('-pa', '--palette', action='store_true',
help='Color Palette, use with shades option, needs work.')
parser.add_argument('-s', '--shades', type=int, default=16,
help='Number of shades, default 16')
parser.add_argument('-wv', '--whitevalue', type=int, default=255,
help='White value, defaults to 255, anything larger than this is skipped.')
parser.add_argument('-de', '--density', type=float, default=2.0,
help='Pixels per MM, default 2.0')
parser.add_argument('-sr', '--skiprate', type=int, default=3000,
help='Moving Feed Rate')
parser.add_argument('-br', '--burnrate', type=int, default=800,
help='Burning Feed Rate')
parser.add_argument('-st', '--steps', type=int, default=255,
help='Laser PWM Steps')
parser.add_argument('-hp', '--highpower', type=int, default=12000,
help='Laser Max Power PWM VAlUE')
parser.add_argument('-lp', '--lowpower', type=int, default=0,
help='Laser Min Power PWM VAlUE')
parser.add_argument('-on', '--laseron', default="M3",
help='Laser ON Gcode Command default: M3')
parser.add_argument('-off', '--laseroff', default="M5",
help='Laser Off Gcode Command default: M5')
parser.add_argument('-mod', '--modifier', default="S",
help='Laser Power Modifier, defaults to Spindle Speed (S)')
parser.add_argument('-o', '--output', default="workfile",
help='Outfile name prefix')
parser.add_argument('-p', '--preview', action='store_true',
help='Preview burn output, red is skipped over.')
parser.add_argument('-gr', '--grblver', type=float, default=0.9,
help='Default Grbl version is 0.9')
parser.add_argument('-tp', '--testpattern', action='store_true',
help='Create a test pattern. Use ./cli.py test 100 -tp -p -o testfile')
parser.add_argument('-d', '--debug', action='store_true',
help='Turns on Debugging')
#Check the arguments
args = parser.parse_args()
#Make sure at least one option is chosen
if not (args.file or args.testpattern):
#Print help
parser.print_help()
#Exit out with no action message
parser.error('No action requested')
if args.lowpower == 0:
args.lowpower = math.ceil(args.highpower/args.steps)
if args.debug:
print "Low power set to: " + str(args.lowpower)
if args.testpattern:
gradientTest()
args.file = "gradient_testpatten.jpg"
#Do all the things
if args.file:
#Load a image file to array
arr = loadImage(args.file)
scaley = 1/args.density
scalex = 1/args.density
#Create a list to store the output gcode lines
lines = []
appendGcode(";Xburn: " + str(args))
#Y position
yp=0
#if the grbl version allows lasermode, enable it
if args.grblver > 0.9:
laserMode(1)
#Turn the laser off
laserOff()
if args.preview:
prv = Image.new( 'RGB', (len(arr[0]),len(arr)), "red")
pixels = prv.load() # create the pixel map
#Work in MM
appendGcode("G21")
#Loop over the list
for y in arr:
#If we have an even number for a y axis line
if yp % 2 != 0:
#Direction is reversed, set xp to the end
xp = len(y) - 1
#Revese the values of y
y = list(reversed(y))
rev = True
else:
xp = 0
rev = False
start = False
#reset the lastxp position
lastxp = 0
#Group pixels by value into a new list
for i, j in groupby(y):
#items in the list
items = list(j)
#Number of items
size = len(items)
#grey Value in this chunk of the line
value = items[0]
#Make sure this group isn't above the whitevalue
if value < args.whitevalue:
if start == False:
#Go the start of this line, the first place with a value
appendGcode("G0 X"+str(round(xp*scaley, 3))+" Y" +
str(round(yp*scaley, 3)) + " F" + str(args.skiprate))
start = True
else:
appendGcode("G0 X" + str(round(xp*scalex, 3)) +
" F" + str(args.skiprate))
#Create the preview
if args.preview:
pvx = len(items)-1 if rev else 0
for item in items:
pix = xp-pvx if rev else xp+pvx
pixels[pix,yp] = (item, item, item)
pvx = pvx-1 if rev else pvx+1
#Turn on the laser
step = (args.highpower-args.lowpower)/args.steps
laserOn(math.ceil((args.steps-value)*step))
#Burn the segment
goto = xp - size if rev else xp + size
appendGcode("G1 X" + str(round((goto)*scalex,3)) +
" F" + str(args.burnrate))
laserOff()
else:
#skip the white
if xp > 0 and xp < len(y) and lastxp == xp:
goto = xp - size if rev else xp + size
appendGcode("G0 X" + str(round((goto)*scalex,3)) +
" F" + str(args.skiprate))
#track x position
lastxp = xp
#Increment position
xp = xp - size if rev else xp + size
yp = yp + 1
#Turn the laser off
laserOff()
#Go to zero
appendGcode("G00 X0 Y0 F" + str(args.skiprate))
#if the grbl version allows lasermode, disable it
if args.grblver > 0.9:
laserMode(0)
#Show a preview if enabled
if args.preview:
prv.transpose(Image.ROTATE_180).transpose(Image.FLIP_LEFT_RIGHT).show()
#Output the gcode file
f = open(args.output+'.gcode', 'w')
f.write("\n".join(lines))