def zigZagLine(self, block, pos, du, dv, U, V, n, extra=0.): sgn = math.copysign(1.0, n) n = abs(n) # Make additional overcut/cuts to compensate for the # round edges in the inner teeths if self.r > 0.0: overcut = self.overcut #rd = (sqrt(2.)-1.0) * self.r rd = (1.0-1.0/sqrt(2.)) * (1.0+self.overcutAdd) * self.r else: overcut = None for i in range(n): # if sgn<0.0 and overcut=="U": # pos -= self.r*U # block.append(CNC.glinev(1, pos, self.feed)) # pos += self.r*U # block.append(CNC.glinev(1, pos)) x = du if sgn<0.0 and n>1: if 0 < i < n-1: x -= 2*self.r else: x -= self.r if i==0: x += extra elif i==n-1: x += extra pos += x*U if i==0: block.append(CNC.glinev(1, pos, self.feed)) else: block.append(CNC.glinev(1, pos)) # if sgn<0.0 and overcut=="U": # pos += self.r*U # block.append(CNC.glinev(1, pos)) # pos -= self.r*U # block.append(CNC.glinev(1, pos)) if self.r>0.0: if sgn<0.0: if i<n-1: if overcut == "V": pos -= sgn*self.r*V block.append(CNC.glinev(1, pos)) pos += sgn*dv*V elif overcut == "D": pos -= sgn*rd*(U+V) block.append(CNC.glinev(1, pos)) pos += sgn*rd*(U+V) block.append(CNC.glinev(1, pos)) pos += sgn*(dv-self.r)*V # else: # pos += sgn*(dv-self.r)*V block.append(CNC.glinev(1, pos)) ijk = self.r*U pos += sgn*self.r*V + self.r*U block.append(CNC.garcv(3, pos, ijk)) else: # ending ijk = self.r*V pos += self.r*V + self.r*U block.append(CNC.garcv(3, pos, ijk)) elif sgn>0.0: ijk = sgn*self.r*V pos += sgn*self.r*V + self.r*U block.append(CNC.garcv(3, pos, ijk)) if i<n-1: if overcut == "V": pos += sgn*dv*V block.append(CNC.glinev(1, pos)) if self.r > 0.0: pos -= sgn*self.r*V block.append(CNC.glinev(1, pos)) elif overcut == "D": pos += sgn*(dv-self.r)*V block.append(CNC.glinev(1, pos)) if self.r > 0.0: pos -= sgn*rd*(U-V) block.append(CNC.glinev(1, pos)) pos += sgn*rd*(U-V) block.append(CNC.glinev(1, pos)) # else: # pos += sgn*(dv-self.r)*V # block.append(CNC.glinev(1, pos)) elif i<n-1: pos += sgn*dv*V block.append(CNC.glinev(1, pos)) sgn = -sgn return pos
def execute(self, app): n = self["name"] if not n or n == "default": n = "Pyrograph" #Import parameters toolSize = self.fromMm("ToolSize") if toolSize <= 0: app.setStatus(_("Pyrograph abort: Tool Size must be > 0")) return filename = self["File"] #Open gcode file if not (filename == ""): app.load(filename) inOut = self["In-Out"] xadd = self["xAdd"] yadd = self["yAdd"] if xadd == "": xadd = 1 if yadd == "": yadd = 1 #Create the external box if inOut == "Out": box = Block("Box") external_box = [] box.append( CNC.grapid(CNC.vars["xmin"] - xadd, CNC.vars["ymin"] - yadd)) box.append( CNC.gline(CNC.vars["xmin"] - xadd, CNC.vars["ymax"] + yadd)) box.append( CNC.gline(CNC.vars["xmax"] + xadd, CNC.vars["ymax"] + yadd)) box.append( CNC.gline(CNC.vars["xmax"] + xadd, CNC.vars["ymin"] - yadd)) box.append( CNC.gline(CNC.vars["xmin"] - xadd, CNC.vars["ymin"] - yadd)) #Insert the external block external_box.append(box) app.gcode.insBlocks(1, external_box, "External_Box") app.refresh() #Value for creating an offset from the margins of the gcode margin = self.fromMm( "Margin" ) #GIVING RANDOM DECIMALS SHOULD AVOID COINCIDENT SEGMENTS BETWEEN ISLAND AND BASE PATHS THAT CONFUSE THE ALGORITHM. WORKS IN MOST CASES. #Add and subtract the margin max_x = CNC.vars["xmax"] + margin min_x = CNC.vars["xmin"] - margin max_y = CNC.vars["ymax"] + margin min_y = CNC.vars["ymin"] - margin #Difference between offtset margins dx = max_x - min_x dy = max_y - min_y #Number of vertical divisions according to the toolsize divisions = dy / toolSize #Distance between horizontal lines step_y = toolSize n_steps_y = int(divisions) + 1 #Create the snake pattern according to the number of divisions pattern = Block(self.name) pattern_base = [] for n in range(n_steps_y): if n == 0: pattern.append(CNC.grapid(min_x, min_y)) #go to bottom left else: y0 = min_y + step_y * (n - 1) y1 = min_y + step_y * (n) if not (n % 2 == 0): pattern.append(CNC.glinev( 1, [max_x, y0])) #write odd lines from left to right pattern.append(CNC.grapid(max_x, y1)) else: pattern.append(CNC.glinev( 1, [min_x, y0])) #write even lines from right to left pattern.append(CNC.grapid(min_x, y1)) #Insert the pattern block pattern_base.append(pattern) app.gcode.insBlocks(1, pattern_base, "pattern") app.refresh() #Mark pattern as island for bid in pattern_base: app.gcode.island([1]) #Select all blocks app.editor.selectAll() paths_base = [] paths_isl = [] points = [] #Compare blocks to separate island from other blocks for bid in app.editor.getSelectedBlocks(): if app.gcode[bid].operationTest('island'): paths_isl.extend(app.gcode.toPath(bid)) else: paths_base.extend(app.gcode.toPath(bid)) #Make intersection between blocks while len(paths_base) > 0: base = paths_base.pop() for island in paths_isl: #points.extend(base.intersectPath(island)) points.extend(island.intersectPath(base)) ###SORTING POINTS### x = [] y = [] #Get (x,y) intersection points for i in range(len(points)): x.append(points[i][2][0]) y.append(points[i][2][1]) #Save (x,y) intersection points in a matrix matrix = [[0 for i in range(2)] for j in range(len(x))] for i in range(len(x)): matrix[i][0] = x[i] matrix[i][1] = y[i] # for i in range(len(x)): # print('puntos',points[i][0],points[i][1],matrix[i][0], matrix[i][1]) #print(matrix) #Sort points in increasing y coordinate matrix.sort(key=itemgetter(1, 0), reverse=False) # for i in range(len(x)): # print('puntos',points[i][0],points[i][1],matrix[i][0], matrix[i][1]) #print(matrix) index = 0 pair = 0 new_matrix = [] for i in range(len(x)): if i == 0: index = index + 1 elif i < len(x) - 1: if matrix[i][1] == matrix[i - 1][1]: index = index + 1 else: if pair % 2 == 0: logic = False else: logic = True submatrix = matrix[i - index:i] submatrix.sort(key=itemgetter(0, 1), reverse=logic) new_matrix.extend(submatrix) index = 1 pair = pair + 1 else: #print('entered') if pair % 2 == 0: logic = False else: logic = True if matrix[i][1] == matrix[i - 1][1]: submatrix = matrix[i - index:] submatrix.sort(key=itemgetter(0, 1), reverse=logic) new_matrix.extend(submatrix) else: index = 1 submatrix = matrix[-1] new_matrix.extend(submatrix) # for i in range(len(x)): # print('puntos',new_matrix[i][0], new_matrix[i][1]) # for i in range(len(x)): # print('puntos',new_matrix[i][0], new_matrix[i][1]) #print(x, y) ###SORTING POINTS END### #Generate the gcode from points obtained blocks = [] block = Block(self.name) for i in range(len(x)): if i == 0: block.append(CNC.grapid(new_matrix[0][0], new_matrix[0][1])) inside = 0 else: if inside == 0: block.append(CNC.gline(new_matrix[i][0], new_matrix[i][1])) inside = 1 else: block.append(CNC.grapid(new_matrix[i][0], new_matrix[i][1])) inside = 0 # for i in range(len(x)): # print('puntos',x[i], y[i]) blocks.append(block) app.gcode.delBlockUndo(1) app.gcode.insBlocks(1, blocks, "Line to Line Burning") app.editor.disable() for block in blocks: block.enable = 1 #app.editor.enable() #app.editor.unselectAll() app.refresh() app.setStatus(_("Generated Line to Line Burning"))
def execute(self, app): try: import midiparser as midiparser except: app.setStatus(_("Error: This plugin requires midiparser.py")) return n = self["name"] if not n or n=="default": n="Midi2CNC" fileName = self["File"] x=0.0 y=0.0 z=0.0 x_dir=1.0; y_dir=1.0; z_dir=1.0; # List of MIDI channels (instruments) to import. # Channel 10 is percussion, so better to omit it channels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] axes = self["AxisUsed"] active_axes = len(axes) transpose = (0,0,0) ppu = [ 200, 200, 200 ] ppu[0] = self["ppu_X"] ppu[1] = self["ppu_X"] ppu[2] = self["ppu_X"] safemin = [ 0, 0, 0 ] safemax = [ 100, 100, 50 ] safemax[0] = self["max_X"] safemax[1] = self["max_Y"] safemax[2] = self["max_Z"] try: midi = midiparser.File(fileName) except: app.setStatus(_("Error: Sorry can't parse the Midi file.")) return noteEventList=[] all_channels=set() for track in midi.tracks: #channels=set() for event in track.events: if event.type == midiparser.meta.SetTempo: tempo=event.detail.tempo # filter undesired instruments if ((event.type == midiparser.voice.NoteOn) and (event.channel in channels)): if event.channel not in channels: channels.add(event.channel) # NB: looks like some use "note on (vel 0)" as equivalent to note off, so check for vel=0 here and treat it as a note-off. if event.detail.velocity > 0: noteEventList.append([event.absolute, 1, event.detail.note_no, event.detail.velocity]) else: noteEventList.append([event.absolute, 0, event.detail.note_no, event.detail.velocity]) if (event.type == midiparser.voice.NoteOff) and (event.channel in channels): if event.channel not in channels: channels.add(event.channel) noteEventList.append([event.absolute, 0, event.detail.note_no, event.detail.velocity]) # Finished with this track if len(channels) > 0: msg=', ' . join(['%2d' % ch for ch in sorted(channels)]) #print 'Processed track %d, containing channels numbered: [%s ]' % (track.number, msg) all_channels = all_channels.union(channels) # List all channels encountered if len(all_channels) > 0: msg=', ' . join(['%2d' % ch for ch in sorted(all_channels)]) #print 'The file as a whole contains channels numbered: [%s ]' % msg # We now have entire file's notes with abs time from all channels # We don't care which channel/voice is which, but we do care about having all the notes in order # so sort event list by abstime to dechannelify noteEventList.sort() # print noteEventList # print len(noteEventList) last_time=-0 active_notes={} # make this a dict so we can add and remove notes by name # Start the output #Init blocks blocks = [] block = Block(self.name) block.append("(Midi2CNC)") block.append("(Midi:%s)" % fileName) block.append(CNC.zsafe()) block.append(CNC.grapid(0,0)) block.append(CNC.zenter(0)) for note in noteEventList: # note[timestamp, note off/note on, note_no, velocity] if last_time < note[0]: freq_xyz=[0,0,0] feed_xyz=[0,0,0] distance_xyz=[0,0,0] duration=0 # "i" ranges from 0 to "the number of active notes *or* the number of active axes, # whichever is LOWER". Note that the range operator stops # short of the maximum, so this means 0 to 2 at most for a 3-axis machine. # E.g. only look for the first few active notes to play despite what # is going on in the actual score. for i in range(0, min(len(active_notes.values()), active_axes)): # Which axis are should we be writing to? # j = self.axes_dict.get(axes)[i] # Debug # print"Axes %s: item %d is %d" % (axes_dict.get(args.axes), i, j) # Sound higher pitched notes first by sorting by pitch then indexing by axis # nownote=sorted(active_notes.values(), reverse=True)[i] # MIDI note 69 = A4(440Hz) # 2 to the power (69-69) / 12 * 440 = A4 440Hz # 2 to the power (64-69) / 12 * 440 = E4 329.627Hz # freq_xyz[j] = pow(2.0, (nownote-69 + transpose[j])/12.0)*440.0 # Here is where we need smart per-axis feed conversions # to enable use of X/Y *and* Z on a Makerbot # # feed_xyz[0] = X; feed_xyz[1] = Y; feed_xyz[2] = Z; # # Feed rate is expressed in mm / minutes so 60 times # scaling factor is required. feed_xyz[j] = ( freq_xyz[j] * 60.0 ) / ppu[j] # Get the duration in seconds from the MIDI values in divisions, at the given tempo duration = ( ( ( note[0] - last_time ) + 0.0 ) / ( midi.division + 0.0 ) * ( tempo / 1000000.0 ) ) # Get the actual relative distance travelled per axis in mm distance_xyz[j] = ( feed_xyz[j] * duration ) / 60.0 # Now that axes can be addressed in any order, need to make sure # that all of them are silent before declaring a rest is due. if distance_xyz[0] + distance_xyz[1] + distance_xyz[2] > 0: # At least one axis is playing, so process the note into # movements combined_feedrate = math.sqrt(feed_xyz[0]**2 + feed_xyz[1]**2 + feed_xyz[2]**2) # Turn around BEFORE crossing the limits of the # safe working envelope if self.reached_limit( x, distance_xyz[0], x_dir, safemin[0], safemax[0] ): x_dir = x_dir * -1 x = (x + (distance_xyz[0] * x_dir)) if self.reached_limit( y, distance_xyz[1], y_dir, safemin[1], safemax[1] ): y_dir = y_dir * -1 y = (y + (distance_xyz[1] * y_dir)) if self.reached_limit( z, distance_xyz[2], z_dir, safemin[2], safemax[2] ): z_dir = z_dir * -1 z = (z + (distance_xyz[2] * z_dir)) v = (x,y,z) block.append(CNC.glinev(1,v,combined_feedrate)) else: # Handle 'rests' in addition to notes. duration = (((note[0]-last_time)+0.0)/(midi.division+0.0)) * (tempo/1000000.0) block.append(CNC.gcode(4, [("P",duration)])) # finally, set this absolute time as the new starting time last_time = note[0] if note[1]==1: # Note on if active_notes.has_key(note[2]): pass else: # key and value are the same, but we don't really care. active_notes[note[2]]=note[2] elif note[1]==0: # Note off if(active_notes.has_key(note[2])): active_notes.pop(note[2]) blocks.append(block) active = app.activeBlock() if active==0: active=1 app.gcode.insBlocks(active, blocks, "Midi2CNC") app.refresh() app.setStatus(_("Generated Midi2CNC, ready to play?"))
def execute(self, app): try: import midiparser as midiparser except: app.setStatus(_("Error: This plugin requires midiparser.py")) return n = self["name"] if not n or n == "default": n = "Midi2CNC" fileName = self["File"] x = 0.0 y = 0.0 z = 0.0 x_dir = 1.0 y_dir = 1.0 z_dir = 1.0 # List of MIDI channels (instruments) to import. # Channel 10 is percussion, so better to omit it channels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] axes = self["AxisUsed"] active_axes = len(axes) transpose = (0, 0, 0) ppu = [200, 200, 200] ppu[0] = self["ppu_X"] ppu[1] = self["ppu_X"] ppu[2] = self["ppu_X"] safemin = [0, 0, 0] safemax = [100, 100, 50] safemax[0] = self["max_X"] safemax[1] = self["max_Y"] safemax[2] = self["max_Z"] try: midi = midiparser.File(fileName) except: app.setStatus(_("Error: Sorry can't parse the Midi file.")) return noteEventList = [] all_channels = set() for track in midi.tracks: #channels=set() for event in track.events: if event.type == midiparser.meta.SetTempo: tempo = event.detail.tempo # filter undesired instruments if ((event.type == midiparser.voice.NoteOn) and (event.channel in channels)): if event.channel not in channels: channels.add(event.channel) # NB: looks like some use "note on (vel 0)" as equivalent to note off, so check for vel=0 here and treat it as a note-off. if event.detail.velocity > 0: noteEventList.append([ event.absolute, 1, event.detail.note_no, event.detail.velocity ]) else: noteEventList.append([ event.absolute, 0, event.detail.note_no, event.detail.velocity ]) if (event.type == midiparser.voice.NoteOff) and (event.channel in channels): if event.channel not in channels: channels.add(event.channel) noteEventList.append([ event.absolute, 0, event.detail.note_no, event.detail.velocity ]) # Finished with this track if len(channels) > 0: msg = ', '.join(['%2d' % ch for ch in sorted(channels)]) #print 'Processed track %d, containing channels numbered: [%s ]' % (track.number, msg) all_channels = all_channels.union(channels) # List all channels encountered if len(all_channels) > 0: msg = ', '.join(['%2d' % ch for ch in sorted(all_channels)]) #print 'The file as a whole contains channels numbered: [%s ]' % msg # We now have entire file's notes with abs time from all channels # We don't care which channel/voice is which, but we do care about having all the notes in order # so sort event list by abstime to dechannelify noteEventList.sort() # print noteEventList # print len(noteEventList) last_time = -0 active_notes = { } # make this a dict so we can add and remove notes by name # Start the output #Init blocks blocks = [] block = Block(self.name) block.append("(Midi2CNC)") block.append("(Midi:%s)" % fileName) block.append(CNC.zsafe()) block.append(CNC.grapid(0, 0)) block.append(CNC.zenter(0)) for note in noteEventList: # note[timestamp, note off/note on, note_no, velocity] if last_time < note[0]: freq_xyz = [0, 0, 0] feed_xyz = [0, 0, 0] distance_xyz = [0, 0, 0] duration = 0 # "i" ranges from 0 to "the number of active notes *or* the number of active axes, # whichever is LOWER". Note that the range operator stops # short of the maximum, so this means 0 to 2 at most for a 3-axis machine. # E.g. only look for the first few active notes to play despite what # is going on in the actual score. for i in range(0, min(len(active_notes.values()), active_axes)): # Which axis are should we be writing to? # j = self.axes_dict.get(axes)[i] # Debug # print"Axes %s: item %d is %d" % (axes_dict.get(args.axes), i, j) # Sound higher pitched notes first by sorting by pitch then indexing by axis # nownote = sorted(active_notes.values(), reverse=True)[i] # MIDI note 69 = A4(440Hz) # 2 to the power (69-69) / 12 * 440 = A4 440Hz # 2 to the power (64-69) / 12 * 440 = E4 329.627Hz # freq_xyz[j] = pow( 2.0, (nownote - 69 + transpose[j]) / 12.0) * 440.0 # Here is where we need smart per-axis feed conversions # to enable use of X/Y *and* Z on a Makerbot # # feed_xyz[0] = X; feed_xyz[1] = Y; feed_xyz[2] = Z; # # Feed rate is expressed in mm / minutes so 60 times # scaling factor is required. feed_xyz[j] = (freq_xyz[j] * 60.0) / ppu[j] # Get the duration in seconds from the MIDI values in divisions, at the given tempo duration = (((note[0] - last_time) + 0.0) / (midi.division + 0.0) * (tempo / 1000000.0)) # Get the actual relative distance travelled per axis in mm distance_xyz[j] = (feed_xyz[j] * duration) / 60.0 # Now that axes can be addressed in any order, need to make sure # that all of them are silent before declaring a rest is due. if distance_xyz[0] + distance_xyz[1] + distance_xyz[2] > 0: # At least one axis is playing, so process the note into # movements combined_feedrate = math.sqrt(feed_xyz[0]**2 + feed_xyz[1]**2 + feed_xyz[2]**2) # Turn around BEFORE crossing the limits of the # safe working envelope if self.reached_limit(x, distance_xyz[0], x_dir, safemin[0], safemax[0]): x_dir = x_dir * -1 x = (x + (distance_xyz[0] * x_dir)) if self.reached_limit(y, distance_xyz[1], y_dir, safemin[1], safemax[1]): y_dir = y_dir * -1 y = (y + (distance_xyz[1] * y_dir)) if self.reached_limit(z, distance_xyz[2], z_dir, safemin[2], safemax[2]): z_dir = z_dir * -1 z = (z + (distance_xyz[2] * z_dir)) v = (x, y, z) block.append(CNC.glinev(1, v, combined_feedrate)) else: # Handle 'rests' in addition to notes. duration = (((note[0] - last_time) + 0.0) / (midi.division + 0.0)) * (tempo / 1000000.0) block.append(CNC.gcode(4, [("P", duration)])) # finally, set this absolute time as the new starting time last_time = note[0] if note[1] == 1: # Note on if active_notes.has_key(note[2]): pass else: # key and value are the same, but we don't really care. active_notes[note[2]] = note[2] elif note[1] == 0: # Note off if (active_notes.has_key(note[2])): active_notes.pop(note[2]) blocks.append(block) active = app.activeBlock() if active == 0: active = 1 app.gcode.insBlocks(active, blocks, "Midi2CNC") app.refresh() app.setStatus(_("Generated Midi2CNC, ready to play?"))
def zigZagLine(self, block, pos, du, dv, U, V, n, extra=0.): sgn = math.copysign(1.0, n) n = abs(n) # Make additional overcut/cuts to compensate for the # round edges in the inner teeth if self.r > 0.0: overcut = self.overcut #rd = (sqrt(2.)-1.0) * self.r rd = (1.0 - 1.0 / sqrt(2.)) * (1.0 + self.overcutAdd) * self.r else: overcut = None for i in range(n): # if sgn<0.0 and overcut=="U": # pos -= self.r*U # block.append(CNC.glinev(1, pos, self.feed)) # pos += self.r*U # block.append(CNC.glinev(1, pos)) x = du if sgn < 0.0 and n > 1: if 0 < i < n - 1: x -= 2 * self.r else: x -= self.r if i == 0: x += extra elif i == n - 1: x += extra pos += x * U if i == 0: block.append(CNC.glinev(1, pos, self.feed)) else: block.append(CNC.glinev(1, pos)) # if sgn<0.0 and overcut=="U": # pos += self.r*U # block.append(CNC.glinev(1, pos)) # pos -= self.r*U # block.append(CNC.glinev(1, pos)) if self.r > 0.0: if sgn < 0.0: if i < n - 1: if overcut == "V": pos -= sgn * self.r * V block.append(CNC.glinev(1, pos)) pos += sgn * dv * V elif overcut == "D": pos -= sgn * rd * (U + V) block.append(CNC.glinev(1, pos)) pos += sgn * rd * (U + V) block.append(CNC.glinev(1, pos)) pos += sgn * (dv - self.r) * V else: pos += sgn * (dv - self.r) * V block.append(CNC.glinev(1, pos)) ijk = self.r * U pos += sgn * self.r * V + self.r * U block.append(CNC.garcv(3, pos, ijk)) else: # ending ijk = self.r * V pos += self.r * V + self.r * U block.append(CNC.garcv(3, pos, ijk)) elif sgn > 0.0: ijk = sgn * self.r * V pos += sgn * self.r * V + self.r * U block.append(CNC.garcv(3, pos, ijk)) if i < n - 1: if overcut == "V": pos += sgn * dv * V block.append(CNC.glinev(1, pos)) if self.r > 0.0: pos -= sgn * self.r * V block.append(CNC.glinev(1, pos)) elif overcut == "D": pos += sgn * (dv - self.r) * V block.append(CNC.glinev(1, pos)) if self.r > 0.0: pos -= sgn * rd * (U - V) block.append(CNC.glinev(1, pos)) pos += sgn * rd * (U - V) block.append(CNC.glinev(1, pos)) else: pos += sgn * (dv - self.r) * V block.append(CNC.glinev(1, pos)) elif i < n - 1: pos += sgn * dv * V block.append(CNC.glinev(1, pos)) sgn = -sgn return pos
def execute(self, app): try: from PIL import Image except: app.setStatus(_("Pyrograph abort: This plugin requires PIL/Pillow")) return n = self["name"] if not n or n=="default": n="Pyrograph" #Calc desired size toolSize = self.fromMm("ToolSize") maxSize = self.fromMm("MaxSize") feedMin = self["FeedMin"] feedMax = self["FeedMax"] depth = self.fromMm("Depth") direction = self["Direction"] drawBorder = self["DrawBorder"] #Check parameters if direction is "": app.setStatus(_("Pyrograph abort: please define a scan Direction")) return if toolSize <=0: app.setStatus(_("Pyrograph abort: Tool Size must be > 0")) return if feedMin <=0 or feedMax <=0 : app.setStatus(_("Pyrograph abort: Please check feed rate parameters")) return #divisions divisions = maxSize / toolSize fileName = self["File"] try: img = Image.open(fileName) img = img.convert ('RGB') #be sure to have color to calculate luminance except: app.setStatus(_("Pyrograph abort: Can't read image file")) return iWidth,iHeight = img.size newWidth = iWidth newHeight = iHeight ratio = 1 if (iWidth > iHeight): ratio = float(iWidth) / float(iHeight) newWidth = int(divisions) newHeight = int(divisions / ratio) else: ratio = float(iHeight) / float(iWidth) newWidth = int(divisions / ratio) newHeight = int(divisions) #Create a thumbnail image to work faster img.thumbnail((newWidth,newHeight), Image.ANTIALIAS) newWidth,newHeight = img.size #img.save("thumb.png") pixels = list(img.getdata()) #Extract luminance gMap = [] for x in range(0,newWidth): gRow = [] for y in range(0,newHeight): R,G,B = pixels[(y * newWidth) + x ] L = (0.299*R + 0.587*G + 0.114*B) #Luminance (Rec. 601 standard) gRow.append(L) gMap.append(gRow) #Init blocks blocks = [] block = Block(self.name) block.append("(Pyrograph W=%g x H=%g x D=%g)" % (newWidth * toolSize , newHeight * toolSize , depth)) #Create points for vertical scan xH = [] yH = [] fH = [] if (direction=="Vertical" or direction=="Both"): r = range(0,newHeight) for x in range(0,newWidth): r = r[::-1] fPrec = -1 for y in r: f = int(feedMin + ((feedMax - feedMin) * gMap[x][y] / 255.0)) if(f != fPrec or y==0 or y==newHeight-1): xH.append(x * toolSize) yH.append((newHeight-y) * toolSize) fH.append(f) fPrec = f #Create points for horizontal scan xV = [] yV = [] fV = [] if (direction=="Horizontal" or direction=="Both"): r = range(0,newWidth) for y in reversed(range(0,newHeight)): fPrec = -1 for x in r: f = int(feedMin + ((feedMax - feedMin) * gMap[x][y] / 255.0)) if(f != fPrec or x==0 or x==newWidth-1): xV.append(x * toolSize) yV.append((newHeight-y) * toolSize) fV.append(f) fPrec = f r = r[::-1] #Gcode Horizontal if (len(xH)>1 and len(yH)>1): block.append(CNC.zsafe()) block.append(CNC.grapid(xH[0],yH[0])) block.append(CNC.zenter(depth)) for x,y,f in zip(xH,yH,fH): v = (x,y,depth) block.append(CNC.glinev(1,v,f)) #Gcode Vertical if (len(xV)>1 and len(yV)>1): block.append(CNC.zsafe()) block.append(CNC.grapid(xV[0],yV[0])) block.append(CNC.zenter(depth)) for x,y,f in zip(xV,yV,fV): v = (x,y,depth) block.append(CNC.glinev(1,v,f)) #Draw Border if required if drawBorder: block.append(CNC.zsafe()) block.append(CNC.grapid(0,0)) block.append(CNC.zenter(depth)) block.append(CNC.gcode(1, [("f",feedMin)])) block.append(CNC.gline(newWidth * toolSize - toolSize,0)) block.append(CNC.gline(newWidth * toolSize - toolSize ,newHeight* toolSize)) block.append(CNC.gline(0,newHeight* toolSize )) block.append(CNC.gline(0,0)) #Gcode Zsafe block.append(CNC.zsafe()) blocks.append(block) active = app.activeBlock() if active==0: active=1 app.gcode.insBlocks(active, blocks, "Pyrograph") app.refresh() app.setStatus(_("Generated Pyrograph W=%g x H=%g x D=%g") % (newWidth * toolSize , newHeight * toolSize , depth))
def execute(self, app): try: from PIL import Image except: app.setStatus( _("Pyrograph abort: This plugin requires PIL/Pillow")) return n = self["name"] if not n or n == "default": n = "Pyrograph" #Calc desired size toolSize = self.fromMm("ToolSize") maxSize = self.fromMm("MaxSize") feedMin = self["FeedMin"] feedMax = self["FeedMax"] depth = self.fromMm("Depth") direction = self["Direction"] drawBorder = self["DrawBorder"] #Check parameters if direction is "": app.setStatus(_("Pyrograph abort: please define a scan Direction")) return if toolSize <= 0: app.setStatus(_("Pyrograph abort: Tool Size must be > 0")) return if feedMin <= 0 or feedMax <= 0: app.setStatus( _("Pyrograph abort: Please check feed rate parameters")) return #divisions divisions = maxSize / toolSize fileName = self["File"] try: img = Image.open(fileName) img = img.convert( 'RGB') #be sure to have color to calculate luminance except: app.setStatus(_("Pyrograph abort: Can't read image file")) return iWidth, iHeight = img.size newWidth = iWidth newHeight = iHeight ratio = 1 if (iWidth > iHeight): ratio = float(iWidth) / float(iHeight) newWidth = int(divisions) newHeight = int(divisions / ratio) else: ratio = float(iHeight) / float(iWidth) newWidth = int(divisions / ratio) newHeight = int(divisions) #Create a thumbnail image to work faster img.thumbnail((newWidth, newHeight), Image.ANTIALIAS) newWidth, newHeight = img.size #img.save("thumb.png") pixels = list(img.getdata()) #Extract luminance gMap = [] for x in range(0, newWidth): gRow = [] for y in range(0, newHeight): R, G, B = pixels[(y * newWidth) + x] L = (0.299 * R + 0.587 * G + 0.114 * B ) #Luminance (Rec. 601 standard) gRow.append(L) gMap.append(gRow) #Init blocks blocks = [] block = Block(self.name) block.append("(Pyrograph W=%g x H=%g x D=%g)" % (newWidth * toolSize, newHeight * toolSize, depth)) #Create points for vertical scan xH = [] yH = [] fH = [] if (direction == "Vertical" or direction == "Both"): r = range(0, newHeight) for x in range(0, newWidth): r = r[::-1] fPrec = -1 for y in r: f = int(feedMin + ((feedMax - feedMin) * gMap[x][y] / 255.0)) if (f != fPrec or y == 0 or y == newHeight - 1): xH.append(x * toolSize) yH.append((newHeight - y) * toolSize) fH.append(f) fPrec = f #Create points for horizontal scan xV = [] yV = [] fV = [] if (direction == "Horizontal" or direction == "Both"): r = range(0, newWidth) for y in reversed(range(0, newHeight)): fPrec = -1 for x in r: f = int(feedMin + ((feedMax - feedMin) * gMap[x][y] / 255.0)) if (f != fPrec or x == 0 or x == newWidth - 1): xV.append(x * toolSize) yV.append((newHeight - y) * toolSize) fV.append(f) fPrec = f r = r[::-1] #Gcode Horizontal if (len(xH) > 1 and len(yH) > 1): block.append(CNC.zsafe()) block.append(CNC.grapid(xH[0], yH[0])) block.append(CNC.zenter(depth)) for x, y, f in zip(xH, yH, fH): v = (x, y, depth) block.append(CNC.glinev(1, v, f)) #Gcode Vertical if (len(xV) > 1 and len(yV) > 1): block.append(CNC.zsafe()) block.append(CNC.grapid(xV[0], yV[0])) block.append(CNC.zenter(depth)) for x, y, f in zip(xV, yV, fV): v = (x, y, depth) block.append(CNC.glinev(1, v, f)) #Draw Border if required if drawBorder: block.append(CNC.zsafe()) block.append(CNC.grapid(0, 0)) block.append(CNC.zenter(depth)) block.append(CNC.gcode(1, [("f", feedMin)])) block.append(CNC.gline(newWidth * toolSize - toolSize, 0)) block.append( CNC.gline(newWidth * toolSize - toolSize, newHeight * toolSize)) block.append(CNC.gline(0, newHeight * toolSize)) block.append(CNC.gline(0, 0)) #Gcode Zsafe block.append(CNC.zsafe()) blocks.append(block) active = app.activeBlock() if active == 0: active = 1 app.gcode.insBlocks(active, blocks, "Pyrograph") app.refresh() app.setStatus( _("Generated Pyrograph W=%g x H=%g x D=%g") % (newWidth * toolSize, newHeight * toolSize, depth))