Commit e2f9e3a2 authored by Nicholas Gar Hei Chan's avatar Nicholas Gar Hei Chan
Browse files

Moved gcodeparser into Octoprint plugin

parent e2be9ae8
"""GCODE Parser
Parses a GCODE file and creates a model reconstruction split into layers and lines.
Currently only supports GCODE with linear moves (G0/G1) in absolute mode.
The parse_gcode() function takes a GCODE file and turns it into a Model object.
"""
import re
try:
import matplotlib.pyplot as plt
except:
print("Matplotlib not installed")
class Model():
"""Model class for storing layer and max/min data"""
def __init__(self, layers, max_x, max_y, max_z, min_x, min_y, min_z):
"""
Parameters
----------
layers: [Layer]
List of Layer objects
max_x: float
Maximum x value of model
max_y: float
Maximum y value of model
max_z: float
Maximum z value of model
min_x: float
Minimum x value of model
min_y: float
Minimum y value of model
min_z: float
Minimum z value of model
"""
self.layers = layers
self.max_y = max_y
self.max_x = max_x
self.max_z = max_z
self.min_y = min_y
self.min_x = min_x
self.min_z = min_z
def to_svgs(self, dir_name):
"""Saves layers as SVG images in directory
Parameters
----------
dir_name: str
Directory name to save images into
"""
for layer_index, layer in enumerate(self.layers):
layer.to_svg(self.max_y, self.max_x, dir_name + "/{}.svg".format(layer_index))
class Line():
"""Line class defines a continuous extrusion in a layer"""
def __init__(self):
self.x = []
self.y = []
def append_coords(self, x, y):
self.x.append(x)
self.y.append(y)
def len(self):
return len(self.x) - 1
def is_empty(self):
if self.len() > 0:
return False
else:
return True
class Layer():
"""Layer class contains all lines in a layer, along with z-height info and
functions for updating a layer and converting a layer to an SVG image."""
def __init__(self, z_height):
"""
Parameters
----------
z_height: float
The height of the layer
"""
self.lines = []
self.new_line()
self.prev_e = 0
self.prev_g = -1
self.z_height = z_height
def append_coords(self, x, y, e, g):
"""
Appends coordinates to a line as appropriate. Checks if a command is an extrusion,
if it is, adds the coordinates to a line. Also checks if the line is continuous,
if it is not, starts a new line.
Parameters
----------
x: float
X-coordinate of command
y: float
Y-coordinate of command
e: float
Extursion value
g: int
G-CODE command type (not used)
"""
if e > self.prev_e: # If extrusion is taking place
self.lines[-1].append_coords(x,y) # Append coordinate to line
self.prev_e = e
self.prev_g = g
else: # If no extrusion is taking place
if not self.lines[-1].is_empty(): # And the line object is not empty
self.new_line() # Start a new line
self.lines[-1].x = [x]
self.lines[-1].y = [y]
def new_line(self):
"""Makes a new line"""
self.lines.append(Line())
def to_svg(self, max_height, max_width, fn):
"""
Saves a layer as an SVG image file
Parameters
----------
max_height: float
Maximum height of model in mm
max_width: float
Maximum width of model in mm
fn: str
Filename to save image to
"""
with open(fn, "w") as f:
f.write(('<svg xmlns="http://www.w3.org/2000/svg"'
' xmlns:xlink="http://www.w3.org/1999/xlink"'
' viewBox="0 0 250 40" height="{}mm" width="{}mm">\n').format(max_height, max_width))
for line in self.lines:
points = '\t<polyline points="'
coords = [f"{x},{y} " for x,y in zip(line.x, line.y)]
points = points + "".join(coords) + ('"\n\tstyle="fill:none;stroke:black;stroke-width:0.4;'
'stroke-linejoin:round;stroke-linecap:round" />\n')
f.write(points)
f.write('</svg>')
def to_svg_inline(self, max_height, viewbox_width, viewbox_height):
out = ('<svg xmlns="http://www.w3.org/2000/svg"'
' xmlns:xlink="http://www.w3.org/1999/xlink"'
' viewBox="0 0 {} {}" height="{}" width="100%">\n').format(viewbox_width, viewbox_height, max_height)
for line in self.lines:
points = '\t<polyline points="'
coords = [f"{x},{y} " for x,y in zip(line.x, line.y)]
points = points + "".join(coords) + ('"\n\tstyle="fill:none;stroke:black;stroke-width:0.4;'
'stroke-linejoin:round;stroke-linecap:round" />\n')
out += points
out += '</svg>'
return out
def plot_layer(self):
for line in self.lines:
plt.plot(line.x, line.y, 'xk')
def to_csv(self, fn):
with open(fn, "w") as f:
for line in self.lines:
[f.write("{},{}\n".format(x, y)) for (x,y) in zip(line.x, line.y)]
def len(self):
return len([line for line in self.lines if line.len() > 0])
def get_points(self):
x_pts = []
y_pts = []
[x_pts.extend(line.x) for line in self.lines]
[y_pts.extend(line.y) for line in self.lines]
return dict([('x', x_pts), ('y', y_pts), ('num_pts', len(x_pts))])
def parse_gcode(filename):
layers = []
num_layers = 0
layer_heights = []
lines = []
Gs = []
xs = []
ys = []
zs = []
es = []
prev_x = 0
prev_y = 0
prev_z = 0
prev_e = 0
with open(filename) as f: # Open GCODE file
file = f.readlines()
for line in file: # Iterate through lines
line = line.strip() # Strip newline character
if not line or line[0] == ";": # Skip empty lines and comments
continue
line = line.split(";")[0].strip() # Remove any trailing comments
#TODO: Add handling for G2/G3 and incremental mode
G_match = re.match("^(?:G0|G1)(\.\d+)?\s", line) # Match for any linear movements
if G_match:
lines.append(line)
G = re.match("G([0123])", line) # Match for G command
Gs.append(int(G.group(1)))
x_loc = re.match(".*X(-?\d*\.?\d*)", line) # Match for X coordinates. If none, use prior
if x_loc:
x_loc = float(x_loc.group(1))
prev_x = x_loc
xs.append(x_loc)
else:
xs.append(prev_x)
y_loc = re.match(".*Y(-?\d*\.?\d*)", line) # Match for Y coordinates. If none, use prior
if y_loc:
y_loc = float(y_loc.group(1))
prev_y = y_loc
ys.append(y_loc)
else:
ys.append(prev_y)
z_loc = re.match(".*Z(-?\d*\.?\d*)", line) # Match for Z coordinates. If none, use prior
if z_loc:
z_loc = float(z_loc.group(1))
prev_z = z_loc
zs.append(z_loc)
else:
zs.append(prev_z)
e_loc = re.match(".*E(-?\d*\.?\d*)", line) # Match for Z coordinates. If none, use prior
if e_loc:
e_loc = float(e_loc.group(1))
prev_e = e_loc
es.append(e_loc)
else:
es.append(prev_e)
[layer_heights.append(height) for height in set(zs)] # Find the unique Z heights in the GCODE file
layer_heights.sort() # Make it a sorted list
num_layers = len(layer_heights) # Number of layers
index = dict(zip(layer_heights, range(num_layers))) # Map layer height to an index
layers = [Layer(layer_heights[i]) for i in range(num_layers)] # Initialize model list with number of layers
for i in range(len(lines)): # Iterate through each line
layers[index[zs[i]]].append_coords(xs[i],ys[i],es[i],Gs[i]) # Append each command into the model list
layers = [layer for layer in layers if layer.len() > 0]
model = Model(layers, max(xs), max(ys), max(zs), min(xs), min(ys), min(zs))
return model
......@@ -14,9 +14,7 @@ import octoprint.plugin
import sys
import os
#sys.path.insert(0, '/home/ngchan/devel/gcode-parser/gcode-parser')
#import gcode_parser as gcp
import gcodeparser as gcp
import flask
class AutofdpPlugin(octoprint.plugin.SettingsPlugin,
......@@ -96,6 +94,7 @@ class AutofdpPlugin(octoprint.plugin.SettingsPlugin,
##~~ StartupPlugin mixin
def on_after_startup(self):
self._logger.info(os.getcwd())
self._logger.info("autoFDP starting, retrieving saved settings...")
self.offsets['x'] = float(self._settings.get(['x_offset']))
self.offsets['y'] = float(self._settings.get(['y_offset']))
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment