class StateMachinePygcode(StateMachineBase): """ State Machine reflecting the state of a pygcode virtual machine. https://github.com/fragmuffin/pygcode/wiki/Interpreting-gcode """ def __init__(self, on_update_callback: Callable[[str, Any], None]) -> None: super().__init__(on_update_callback) self._virtual_cnc = Machine() def update(self) -> None: """ Populate this state machine values from self._virtual_cnc. """ self._parse_modal() self.machine_pos = self._virtual_cnc.pos.values self.feed_rate = self._virtual_cnc.mode.feed_rate.word.value def _parse_modal(self) -> None: """ Update current modal group values. """ for modal in self._virtual_cnc.mode.gcodes: modal_bytes = str(modal).encode('utf-8') if modal_bytes in self.MODAL_COMMANDS: modal_group = self.MODAL_COMMANDS[modal_bytes] self.gcode_modal[modal_group] = modal_bytes elif chr(modal_bytes[0]).encode('utf-8') in self.MODAL_COMMANDS: modal_group = self.MODAL_COMMANDS[chr( modal_bytes[0]).encode('utf-8')] self.gcode_modal[modal_group] = modal_bytes else: print("TODO: ", modal) # print(self.gcode_modal) def proces_gcode(self, gcode_block: Block) -> None: """ Have the pygcode VM parse incoming gcode. """ self._virtual_cnc.process_block(gcode_block)
def list(args, gcodes, clearance_height): m = Machine() print("The line numbers where a cut is possible are:") for gcode in gcodes: m.process_gcodes(gcode['gcode']) if clearance_height == m.pos._value['Z']: print(" {:7} at {:6.2f}% in {}".format(gcode["line_no"], gcode["line_no"]*100/gcodes[-1]["line_no"],m.pos))
def readGcode(self, filepath): m = Machine() with open(filepath, 'r') as fh: for line_text in fh.readlines(): self.traj.append((m.pos.values['X'], m.pos.values['Y'])) line = Line(line_text) m.process_block(line.block)
def __init__(self, gcode): self.gcode = gcode # gcode stored in plain text or whatever self.all_positions = [] # list of every xyz position the gcode contains, including 'strokes' and 'seeks'. without separating strokes and # seeks, the order of the self.all_positions cannot be changed self.machine = Machine() # Virtual machine that will simulate running the gcode and extract coordinates self.cities = [] # in TSP parlance, each uninterrupted line to be drawn on the paper is considered a city # unlike usual TSP, our cities must have a different start and end rather than being a single point # therefore each city is a list coordinates. self.cities_lite = [] # in order to find the best tour we only need the start and end points of each city. # we will need the whole city only when we come to output new gcode one day. # so to keep it lite we strip all the in between coordinates plus the z values # also format the data as a simple array for numpy reasons # format is [startX, startY, endX, endY] self.seek_threshold = self.guess_seek_threshold() print('seek threshold is', self.seek_threshold) # if the Z position of the pen is above this value, we will assume it's a seek (flight), not a stroke (city) # self.original_tour = [x for x in range(len(self.cities_lite))] # in TSP parlance the tour is the order in which we visit the cities, ie the whole thing we want to find # we keep the original order of strokes for reference self.parse()
def getClearanceHeight(gcodes): m = Machine() last = m.pos clearance_height = m.pos._value['Z'] for gcode in gcodes: m.process_gcodes(gcode['gcode']) if clearance_height < m.pos._value['Z']: clearance_height = m.pos._value['Z'] return clearance_height
def __init__(self): self.state_pos = { 'state': IDLE, 'mpos': [0.000, 0.000, 0.000], 'wpos': [0.000, 0.000, 0.000], 'plan buf': 0, 'rx buf': 0 } self.mach = Machine()
def get_positions(data): t1 = dt.now() machine = Machine() positions = [] last = [] for i in data.split('\n'): line = Line(i) block = line.block.gcodes for g in block: machine.process_gcodes(g) keys = [] for key in machine.pos.values.keys(): keys.append(machine.pos.values[key]) #positions.append(keys) position = machine.pos.values if position != last: positions.append(position) last = position timer(t1, "processing gcode") return positions
def cut(args, gcodes, clearance_height): non_motion = [] # Select where to cut m = Machine() last = m.pos checkpoint = {'gcode': None, 'pos': m.pos, 'mode': m.mode.distance} for idx, gcode in enumerate(gcodes): m.process_gcodes(gcode['gcode']) if gcode["line_no"] > args.line: break if m.pos == last: non_motion.append(gcode) last = m.pos if clearance_height == m.pos._value['Z']: checkpoint = {'idx': idx, 'gcode': gcode, 'pos': m.pos, 'mode': m.mode.distance} #print("Checkpoint at {}".format(checkpoint)) # If the user wants to cut before the first clearance height, do nothing if checkpoint['gcode'] is None: print("The provided line is not a valid one, use list subcommand for more info. Exiting") return # Dump the non motion commands first for gcode in non_motion: # if not isinstance(gcode['gcode'], GCodeFeedRate): # Ignore feedrates please print(FORMAT.format(str(gcode['gcode']), str(gcode['line_no']), gcode['gcode'].__class__.__name__), file=args.output) # Insert the travel until the checkpoint position gcode = GCodeAbsoluteDistanceMode() print(FORMAT.format(str(gcode), "Cut comp.", gcode.__class__.__name__), file=args.output) gcode = GCodeRapidMove(Z=clearance_height) print(FORMAT.format(str(gcode), "Cut comp.", gcode.__class__.__name__), file=args.output) gcode = GCodeRapidMove(X=checkpoint['pos']._value['X'], Y=checkpoint['pos']._value['Y'], Z=checkpoint['pos']._value['Z']) print(FORMAT.format(str(gcode), "Cut comp.", gcode.__class__.__name__), file=args.output) gcode = checkpoint['mode'] print(FORMAT.format(str(gcode), "Cut comp.", gcode.__class__.__name__), file=args.output) print(CHECKPOINT_TAG, file=args.output) # Dump the rest of the codes from the original file (to preserve formatting) try: args.input.seek(gcodes[checkpoint['idx']+1]['offset']) for line in args.input: print(line, file=args.output, end="") except: pass if args.verify: s = Machine() t = Machine() # Run input gcodes until the selected for gcode in gcodes[:checkpoint['idx']+1]: s.process_gcodes(gcode['gcode']) # Run output gcodes until checkpoint args.output.seek(0) line = args.output.readline() while line: if CHECKPOINT_TAG in line: break for gcode in Line(line).block.gcodes: t.process_gcodes(gcode) line = args.output.readline() args.input.seek(gcodes[checkpoint['idx']+1]['offset']) sline = args.output.readline() tline = args.input.readline() while sline and tline: for gcode in Line(sline).block.gcodes: s.process_gcodes(gcode) for gcode in Line(tline).block.gcodes: t.process_gcodes(gcode) if s.pos!=t.pos: print("\nOutput gcode not verified (source: {}, target: {})".format(s.pos, t.pos)) return sline = args.output.readline() tline = args.input.readline() print("\nOutput gcode verified")
class parse_gcode(object): def __init__(self, gcode): self.gcode = gcode # gcode stored in plain text or whatever self.all_positions = [] # list of every xyz position the gcode contains, including 'strokes' and 'seeks'. without separating strokes and # seeks, the order of the self.all_positions cannot be changed self.machine = Machine() # Virtual machine that will simulate running the gcode and extract coordinates self.cities = [] # in TSP parlance, each uninterrupted line to be drawn on the paper is considered a city # unlike usual TSP, our cities must have a different start and end rather than being a single point # therefore each city is a list coordinates. self.cities_lite = [] # in order to find the best tour we only need the start and end points of each city. # we will need the whole city only when we come to output new gcode one day. # so to keep it lite we strip all the in between coordinates plus the z values # also format the data as a simple array for numpy reasons # format is [startX, startY, endX, endY] self.seek_threshold = self.guess_seek_threshold() print('seek threshold is', self.seek_threshold) # if the Z position of the pen is above this value, we will assume it's a seek (flight), not a stroke (city) # self.original_tour = [x for x in range(len(self.cities_lite))] # in TSP parlance the tour is the order in which we visit the cities, ie the whole thing we want to find # we keep the original order of strokes for reference self.parse() def parse(self): # use the machine to populate the all_positions variable with xyz coordinates t1 = dt.now() previous_position = [] for i in self.gcode.split('\n'): line = Line(i) block = line.block.gcodes for g in block: self.machine.process_gcodes(g) keys = [] for key in self.machine.pos.values.keys(): keys.append(self.machine.pos.values[key]) this_position = self.machine.pos.values if this_position != previous_position: self.all_positions.append(this_position) previous_position = this_position timer(t1, 'parsing') self.get_cities() return self.all_positions def guess_seek_threshold(self): minZ = 0 maxZ = 0 for i in self.all_positions: if "Z" in i.keys(): minZ = min(minZ, i["Z"]) maxZ = max(maxZ, i["Z"]) return (minZ + maxZ) / 2 def get_cities(self): this_city = [] for x, this_position in enumerate(self.all_positions): if x == 0: pass # current_pos = self.all_positions[0] # # if current_pos['Z'] <= self.seek_threshold: # this_city.append(this_position) else: if this_position['Z'] <= self.seek_threshold: this_city.append(this_position) else: if this_city: self.cities.append(this_city) this_city = [] self.cities_lite = [[i[0]['X'], i[0]['Y'], i[-1]['X'], i[-1]['Y']] for i in self.cities] return self.cities
def __init__(self, on_update_callback: Callable[[str, Any], None]) -> None: super().__init__(on_update_callback) self._virtual_cnc = Machine()
# requires PyGCode: pip3 install pygcode from pygcode import GCodeRapidMove, GCodeLinearMove from pygcode import Machine, Line, split_gcodes m = Machine() coordinates = [] pairs = [] with open('triangle.gcode', 'r') as fh: for line_text in fh.readlines(): line = Line(line_text) line.block.gcodes # list of gcodes if line.block.gcodes: # not a blank line (befores, (g, ), afters) = split_gcodes(line.block.gcodes, (GCodeRapidMove, GCodeLinearMove)) if g.X is not None: pairs = [g.X - 150, g.Y] coordinates.append(pairs) print("Number of points:", len(coordinates)) print(coordinates) with open('triangle.json', 'w') as fh: fh.write(repr(coordinates)) i = 0 for line in coordinates: print("G1", "X", coordinates[i][0], "Y", coordinates[i][1], "F2.0") i = i + 1
from pygcode import Line from pygcode import Machine, GCodeRapidMove import re import sys m = Machine() min_x = 0 max_x = 0 min_y = 0 max_y = 0 min_z = 0 max_z = 0 with open(sys.argv[1], 'r') as fh: for line_text in fh.readlines(): ma = re.match(r'^M8.*$', line_text) if (ma): continue ma = re.match(r'^S.*$', line_text) if (ma): continue ma = re.search(r'\sG50\s', line_text) if (ma): line_text = re.sub(r'\sG50\s', '', line_text) line = Line(line_text) m.process_block(line.block) if (m.pos.X < min_x): min_x = m.pos.X if (m.pos.X > max_x): max_x = m.pos.X