Fixed g-code arc parse/plot

This commit is contained in:
Juan Pablo Caram 2014-01-08 01:46:50 -05:00
parent 3cb9e444c0
commit 145496b4ae
9 changed files with 546 additions and 423 deletions

402
camlib.py
View File

@ -7,7 +7,7 @@ import cairo
#import os
#import sys
from numpy import arctan2, Inf, array
from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos
from matplotlib.figure import Figure
# See: http://toblerity.org/shapely/manual.html
@ -16,6 +16,7 @@ from shapely.geometry import MultiPoint, MultiPolygon
from shapely.geometry import box as shply_box
from shapely.ops import cascaded_union
from descartes.patch import PolygonPatch
class Geometry:
def __init__(self):
@ -117,8 +118,6 @@ class Gerber (Geometry):
'''
for region in self.regions:
if region['polygon'].is_valid == False:
#polylist = fix_poly(region['polygon'])
#region['polygon'] = fix_poly3(polylist)
region['polygon'] = region['polygon'].buffer(0)
def buffer_paths(self):
@ -232,6 +231,9 @@ class Gerber (Geometry):
"aperture":last_path_aperture})
def do_flashes(self):
'''
Creates geometry for Gerber flashes (aperture on a single point).
'''
self.flash_geometry = []
for flash in self.flashes:
aperture = self.apertures[flash['aperture']]
@ -263,6 +265,81 @@ class Gerber (Geometry):
[poly['polygon'] for poly in self.regions] +
self.flash_geometry)
class Excellon(Geometry):
def __init__(self):
Geometry.__init__(self)
self.tools = {}
self.drills = []
def parse_file(self, filename):
efile = open(filename, 'r')
estr = efile.readlines()
efile.close()
self.parse_lines(estr)
def parse_lines(self, elines):
'''
Main Excellon parser.
'''
current_tool = ""
for eline in elines:
## Tool definitions ##
# TODO: Verify all this
indexT = eline.find("T")
indexC = eline.find("C")
indexF = eline.find("F")
# Type 1
if indexT != -1 and indexC > indexT and indexF > indexF:
tool = eline[1:indexC]
spec = eline[indexC+1:indexF]
self.tools[tool] = spec
continue
# Type 2
# TODO: Is this inches?
#indexsp = eline.find(" ")
#indexin = eline.find("in")
#if indexT != -1 and indexsp > indexT and indexin > indexsp:
# tool = eline[1:indexsp]
# spec = eline[indexsp+1:indexin]
# self.tools[tool] = spec
# continue
# Type 3
if indexT != -1 and indexC > indexT:
tool = eline[1:indexC]
spec = eline[indexC+1:-1]
self.tools[tool] = spec
continue
## Tool change
if indexT == 0:
current_tool = eline[1:-1]
continue
## Drill
indexX = eline.find("X")
indexY = eline.find("Y")
if indexX != -1 and indexY != -1:
x = float(int(eline[indexX+1:indexY])/10000.0)
y = float(int(eline[indexY+1:-1])/10000.0)
self.drills.append({'point':Point((x,y)), 'tool':current_tool})
continue
print "WARNING: Line ignored:", eline
def create_geometry(self):
self.solid_geometry = []
sizes = {}
for tool in self.tools:
sizes[tool] = float(self.tools[tool])
for drill in self.drills:
poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0)
self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
class CNCjob:
def __init__(self, units="in", kind="generic", z_move = 0.1,
feedrate = 3.0, z_cut = -0.002):
@ -279,7 +356,7 @@ class CNCjob:
self.feedminutecode = "G94"
self.absolutecode = "G90"
# Output G-Code
# Input/Output G-Code
self.gcode = ""
# Bounds of geometry given to CNCjob.generate_from_geometry()
@ -393,6 +470,7 @@ class CNCjob:
self.gcode += "M05\n" # Spindle stop
def create_gcode_geometry(self):
steps_per_circ = 20
'''
G-Code parser (from self.gcode). Generates dictionary with
single-segment LineString's and "kind" indicating cut or travel,
@ -415,26 +493,42 @@ class CNCjob:
current['Z'] = gobj['Z']
if 'G' in gobj:
current['G'] = gobj['G']
current['G'] = int(gobj['G'])
if 'X' in gobj or 'Y' in gobj:
x = 0
y = 0
kind = ["C","F"] # T=travel, C=cut, F=fast, S=slow
if 'X' in gobj:
x = gobj['X']
else:
x = current['X']
if 'Y' in gobj:
y = gobj['Y']
else:
y = current['Y']
if current['Z'] > 0:
kind[0] = 'T'
if current['G'] == 1:
if current['G'] > 0:
kind[1] = 'S'
geometry.append({'geom':LineString([(current['X'],current['Y']),
(x,y)]), 'kind':kind})
arcdir = [None, None, "cw", "ccw"]
if current['G'] in [0,1]: # line
geometry.append({'geom':LineString([(current['X'],current['Y']),
(x,y)]), 'kind':kind})
if current['G'] in [2,3]: # arc
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
radius = sqrt(gobj['I']**2 + gobj['J']**2)
start = arctan2( -gobj['J'], -gobj['I'])
stop = arctan2(-center[1]+y, -center[0]+x)
geometry.append({'geom':arc(center, radius, start, stop,
arcdir[current['G']],
steps_per_circ),
'kind':kind})
# Update current instruction
for code in gobj:
@ -477,153 +571,46 @@ class CNCjob:
ax.add_patch(patch)
return fig
class Excellon(Geometry):
def __init__(self):
Geometry.__init__(self)
self.tools = {}
self.drills = []
def parse_file(self, filename):
efile = open(filename, 'r')
estr = efile.readlines()
efile.close()
self.parse_lines(estr)
def parse_lines(self, elines):
def plot2(self, axes, tooldia=None, dpi=75, margin=0.1,
color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
alpha={"T":0.3, "C":1.0}):
'''
Main Excellon parser.
Plots the G-code job onto the given axes
'''
current_tool = ""
for eline in elines:
if tooldia == None:
tooldia = self.tooldia
## Tool definitions ##
# TODO: Verify all this
indexT = eline.find("T")
indexC = eline.find("C")
indexF = eline.find("F")
# Type 1
if indexT != -1 and indexC > indexT and indexF > indexF:
tool = eline[1:indexC]
spec = eline[indexC+1:indexF]
self.tools[tool] = spec
continue
# Type 2
# TODO: Is this inches?
#indexsp = eline.find(" ")
#indexin = eline.find("in")
#if indexT != -1 and indexsp > indexT and indexin > indexsp:
# tool = eline[1:indexsp]
# spec = eline[indexsp+1:indexin]
# self.tools[tool] = spec
# continue
# Type 3
if indexT != -1 and indexC > indexT:
tool = eline[1:indexC]
spec = eline[indexC+1:-1]
self.tools[tool] = spec
continue
## Tool change
if indexT == 0:
current_tool = eline[1:-1]
continue
## Drill
indexX = eline.find("X")
indexY = eline.find("Y")
if indexX != -1 and indexY != -1:
x = float(int(eline[indexX+1:indexY])/10000.0)
y = float(int(eline[indexY+1:-1])/10000.0)
self.drills.append({'point':Point((x,y)), 'tool':current_tool})
continue
print "WARNING: Line ignored:", eline
#fig = Figure(dpi=dpi)
#ax = fig.add_subplot(111)
#ax.set_aspect(1)
#xmin, ymin, xmax, ymax = self.input_geometry_bounds
#ax.set_xlim(xmin-margin, xmax+margin)
#ax.set_ylim(ymin-margin, ymax+margin)
def create_geometry(self):
self.solid_geometry = []
sizes = {}
for tool in self.tools:
sizes[tool] = float(self.tools[tool])
for drill in self.drills:
poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0)
self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
class motion:
'''
Represents a machine motion, which can be cutting or just travelling.
'''
def __init__(self, start, end, depth, typ='line', offset=None, center=None,
radius=None, tooldia=0.5):
self.typ = typ
self.start = start
self.end = end
self.depth = depth
self.center = center
self.radius = radius
self.tooldia = tooldia
self.offset = offset # (I, J)
if tooldia == 0:
for geo in self.G_geometry:
linespec = '--'
linecolor = color[geo['kind'][0]][1]
if geo['kind'][0] == 'C':
linespec = 'k-'
x, y = geo['geom'].coords.xy
axes.plot(x, y, linespec, color=linecolor)
else:
for geo in self.G_geometry:
poly = geo['geom'].buffer(tooldia/2.0)
patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
edgecolor=color[geo['kind'][0]][1],
alpha=alpha[geo['kind'][0]], zorder=2)
axes.add_patch(patch)
def gparse1(filename):
'''
Parses G-code file into list of dictionaries like
Examples: {'G': 1.0, 'X': 0.085, 'Y': -0.125},
{'G': 3.0, 'I': -0.01, 'J': 0.0, 'X': 0.0821, 'Y': -0.1179}
'''
f = open(filename)
gcmds = []
for line in f:
line = line.strip()
# Remove comments
# NOTE: Limited to 1 bracket pair
op = line.find("(")
cl = line.find(")")
if op > -1 and cl > op:
#comment = line[op+1:cl]
line = line[:op] + line[(cl+1):]
# Parse GCode
# 0 4 12
# G01 X-0.007 Y-0.057
# --> codes_idx = [0, 4, 12]
codes = "NMGXYZIJFP"
codes_idx = []
i = 0
for ch in line:
if ch in codes:
codes_idx.append(i)
i += 1
n_codes = len(codes_idx)
if n_codes == 0:
continue
# Separate codes in line
parts = []
for p in range(n_codes-1):
parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
parts.append( line[codes_idx[-1]:].strip() )
# Separate codes from values
cmds = {}
for part in parts:
cmds[part[0]] = float(part[1:])
gcmds.append(cmds)
f.close()
return gcmds
def gparse1b(gtext):
'''
gtext is a single string with g-code
'''
gcmds = []
lines = gtext.split("\n")
lines = gtext.split("\n") # TODO: This is probably a lot of work!
for line in lines:
line = line.strip()
@ -662,98 +649,43 @@ def gparse1b(gtext):
cmds[part[0]] = float(part[1:])
gcmds.append(cmds)
return gcmds
def gparse2(gcmds):
x = []
y = []
z = []
xypoints = []
motions = []
current_g = None
for cmds in gcmds:
# Destination point
x_ = None
y_ = None
z_ = None
if 'X' in cmds:
x_ = cmds['X']
x.append(x_)
if 'Y' in cmds:
y_ = cmds['Y']
y.append(y_)
if 'Z' in cmds:
z_ = cmds['Z']
z.append(z_)
# Ingnore anything but XY movements from here on
if x_ is None and y_ is None:
#print "-> no x,y"
continue
if x_ is None:
x_ = xypoints[-1][0]
if y_ is None:
y_ = xypoints[-1][1]
if z_ is None:
z_ = z[-1]
mot = None
if 'G' in cmds:
current_g = cmds['G']
if current_g == 0: # Fast linear
if len(xypoints) > 0:
#print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
mot = motion(xypoints[-1], (x_, y_), z_)
if current_g == 1: # Feed-rate linear
if len(xypoints) > 0:
#print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
mot = motion(xypoints[-1], (x_, y_), z_)
if current_g == 2: # Clockwise arc
if len(xypoints) > 0:
if 'I' in cmds and 'J' in cmds:
mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
cmds['J']), typ='arccw')
if current_g == 3: # Counter-clockwise arc
if len(xypoints) > 0:
if 'I' in cmds and 'J' in cmds:
mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
cmds['J']), typ='arcacw')
if mot is not None:
motions.append(mot)
xypoints.append((x_, y_))
x = array(x)
y = array(y)
z = array(z)
xmin = min(x)
xmax = max(x)
ymin = min(y)
ymax = max(y)
print "x:", min(x), max(x)
print "y:", min(y), max(y)
print "z:", min(z), max(z)
print xypoints[-1]
def get_bounds(geometry_sets):
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
return xmin, xmax, ymin, ymax, motions
#geometry_sets = [self.gerbers, self.excellons]
for gs in geometry_sets:
for g in gs:
gxmin, gymin, gxmax, gymax = g.solid_geometry.bounds
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
return [xmin, ymin, xmax, ymax]
def arc(center, radius, start, stop, direction, steps_per_circ):
da_sign = {"cw":-1.0, "ccw":1.0}
points = []
if direction=="ccw" and stop <= start:
stop += 2*pi
if direction=="cw" and stop >= start:
stop -= 2*pi
angle = abs(stop - start)
#angle = stop-start
steps = max([int(ceil(angle/(2*pi)*steps_per_circ)), 2])
delta_angle = da_sign[direction]*angle*1.0/steps
for i in range(steps+1):
theta = start + delta_angle*i
points.append([center[0]+radius*cos(theta), center[1]+radius*sin(theta)])
return LineString(points)
############### cam.py ####################
def coord(gstr,digits,fraction):
'''

Binary file not shown.

View File

@ -29,22 +29,26 @@ class App:
## Event handling ##
self.builder.connect_signals(self)
## Make plot area ##
self.figure = None
self.axes = None
self.canvas = None
## Make plot area ##
self.mplpaint()
self.window.show_all()
########################################
## DATA ##
########################################
self.gerbers = []
self.excellons = []
self.cncjobs = []
self.mouse = None
########################################
## START ##
########################################
self.window.show_all()
Gtk.main()
def mplpaint(self):
@ -58,169 +62,24 @@ class App:
self.axes.grid()
#a.patch.set_visible(False) Background of the axes
self.figure.patch.set_visible(False)
#self.figure.tight_layout()
self.canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea
#self.canvas.set_size_request(600,400)
self.canvas.set_hexpand(1)
self.canvas.set_vexpand(1)
########################################
## EVENTS ##
########################################
self.canvas.mpl_connect('button_press_event', self.on_click_over_plot)
self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot)
##self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot)
##self.canvas.mpl_connect('key_press_event', self.on_key_over_plot)
self.canvas.set_hexpand(1)
self.canvas.set_vexpand(1)
self.canvas.set_can_focus(True) # For key press
self.canvas.mpl_connect('key_press_event', self.on_key_over_plot)
self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot)
#self.builder.get_object("viewport2").add(self.canvas)
self.grid.attach(self.canvas,0,0,600,400)
#self.builder.get_object("scrolledwindow1").add(self.canvas)
def on_filequit(self, param):
print "quit from menu"
self.window.destroy()
Gtk.main_quit()
def on_closewindow(self, param):
print "quit from X"
self.window.destroy()
Gtk.main_quit()
def on_fileopengerber(self, param):
print "File->Open Gerber"
dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = dialog.run()
if response == Gtk.ResponseType.OK:
## Load the file ##
print("Open clicked")
print("File selected: " + dialog.get_filename())
gerber = Gerber()
gerber.parse_file(dialog.get_filename())
self.gerbers.append(gerber)
self.plot_gerber(gerber)
## End ##
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dialog.destroy()
def on_fileopenexcellon(self, param):
print "File->Open Excellon"
dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = dialog.run()
if response == Gtk.ResponseType.OK:
## Load the file ##
print("Open clicked")
print("File selected: " + dialog.get_filename())
excellon = Excellon()
excellon.parse_file(dialog.get_filename())
self.excellons.append(excellon)
self.plot_excellon(excellon)
## End ##
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dialog.destroy()
def plot_gerber(self, gerber):
gerber.create_geometry()
# Options
mergepolys = self.builder.get_object("cb_mergepolys").get_active()
multicolored = self.builder.get_object("cb_multicolored").get_active()
geometry = None
if mergepolys:
geometry = gerber.solid_geometry
else:
geometry = gerber.buffered_paths + \
[poly['polygon'] for poly in gerber.regions] + \
gerber.flash_geometry
linespec = None
if multicolored:
linespec = '-'
else:
linespec = 'k-'
#f = Figure(dpi=75)
#a = f.add_subplot(111)
#a.set_aspect(1)
for poly in geometry:
x, y = poly.exterior.xy
#a.plot(x, y)
self.axes.plot(x, y, linespec)
for ints in poly.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, linespec)
#f.tight_layout()
#canvas = FigureCanvas(f) # a Gtk.DrawingArea
#canvas.set_size_request(600,400)
#self.grid.attach(canvas,1,1,600,400)
#self.window.show_all()
def plot_excellon(self, excellon):
excellon.create_geometry()
# Plot excellon
for geo in excellon.solid_geometry:
x, y = geo.exterior.coords.xy
self.axes.plot(x, y, 'r-')
for ints in geo.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, 'g-')
def on_mouse_move_over_plot(self, event):
try: # May fail in case mouse not within axes
self.positionLabel.set_label("X: %.4f Y: %.4f"%(
event.xdata, event.ydata))
self.mouse = [event.xdata, event.ydata]
except:
self.positionLabel.set_label("X: --- Y: ---")
self.mouse = None
def on_click_over_plot(self, event):
print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
event.button, event.x, event.y, event.xdata, event.ydata)
def get_bounds(self):
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
geometry_sets = [self.gerbers, self.excellons]
for gs in geometry_sets:
for g in gs:
gxmin, gymin, gxmax, gymax = g.solid_geometry.bounds
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
return [xmin, ymin, xmax, ymax]
def on_zoom_in(self, event):
self.zoom(1.5)
return
def on_zoom_out(self, event):
self.zoom(1/1.5)
def on_zoom_fit(self, event):
xmin, ymin, xmax, ymax = self.get_bounds()
width = xmax-xmin
height = ymax-ymin
self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
self.canvas.queue_draw()
return
def zoom(self, factor, center=None):
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
@ -241,34 +100,163 @@ class App:
self.axes.set_ylim((center[1]-new_height*(1-rely), center[1]+new_height*rely))
self.canvas.queue_draw()
def plot_gerber(self, gerber):
gerber.create_geometry()
# def on_scroll_over_plot(self, event):
# print "Scroll"
# center = [event.xdata, event.ydata]
# if sign(event.step):
# self.zoom(1.5, center=center)
# else:
# self.zoom(1/1.5, center=center)
#
# def on_window_scroll(self, event):
# print "Scroll"
#
# def on_key_over_plot(self, event):
# print 'you pressed', event.key, event.xdata, event.ydata
# Options
mergepolys = self.builder.get_object("cb_mergepolys").get_active()
multicolored = self.builder.get_object("cb_multicolored").get_active()
def on_window_key_press(self, widget, event):
print event.get_keycode(), event.get_keyval()
val = int(event.get_keyval()[1])
geometry = None
if mergepolys:
geometry = gerber.solid_geometry
else:
geometry = gerber.buffered_paths + \
[poly['polygon'] for poly in gerber.regions] + \
gerber.flash_geometry
if val == 49: # 1
linespec = None
if multicolored:
linespec = '-'
else:
linespec = 'k-'
for poly in geometry:
x, y = poly.exterior.xy
#a.plot(x, y)
self.axes.plot(x, y, linespec)
for ints in poly.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, linespec)
def plot_excellon(self, excellon):
excellon.create_geometry()
# Plot excellon
for geo in excellon.solid_geometry:
x, y = geo.exterior.coords.xy
self.axes.plot(x, y, 'r-')
for ints in geo.interiors:
x, y = ints.coords.xy
self.axes.plot(x, y, 'g-')
def plot_cncjob(self, job):
job.create_gcode_geometry()
tooldia_text = self.builder.get_object("entry_tooldia").get_text()
tooldia_val = eval(tooldia_text)
job.plot2(self.axes, tooldia=tooldia_val)
return
def file_chooser_action(self, on_success):
dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = dialog.run()
if response == Gtk.ResponseType.OK:
on_success(self, dialog.get_filename())
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dialog.destroy()
########################################
## EVENT HANDLERS ##
########################################
def on_filequit(self, param):
print "quit from menu"
self.window.destroy()
Gtk.main_quit()
def on_closewindow(self, param):
print "quit from X"
self.window.destroy()
Gtk.main_quit()
def on_fileopengerber(self, param):
def on_success(self, filename):
gerber = Gerber()
gerber.parse_file(filename)
self.gerbers.append(gerber)
self.plot_gerber(gerber)
self.file_chooser_action(on_success)
def on_fileopenexcellon(self, param):
def on_success(self, filename):
excellon = Excellon()
excellon.parse_file(filename)
self.excellons.append(excellon)
self.plot_excellon(excellon)
self.file_chooser_action(on_success)
def on_fileopengcode(self, param):
def on_success(self, filename):
f = open(filename)
gcode = f.read()
f.close()
job = CNCjob()
job.gcode = gcode
self.cncjobs.append(job)
self.plot_cncjob(job)
self.file_chooser_action(on_success)
def on_mouse_move_over_plot(self, event):
try: # May fail in case mouse not within axes
self.positionLabel.set_label("X: %.4f Y: %.4f"%(
event.xdata, event.ydata))
self.mouse = [event.xdata, event.ydata]
except:
self.positionLabel.set_label("")
self.mouse = None
def on_click_over_plot(self, event):
# For key presses
self.canvas.grab_focus()
print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
event.button, event.x, event.y, event.xdata, event.ydata)
def on_zoom_in(self, event):
self.zoom(1.5)
return
def on_zoom_out(self, event):
self.zoom(1/1.5)
def on_zoom_fit(self, event):
xmin, ymin, xmax, ymax = get_bounds([self.gerbers, self.excellons])
width = xmax-xmin
height = ymax-ymin
self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
self.canvas.queue_draw()
return
def on_scroll_over_plot(self, event):
print "Scroll"
center = [event.xdata, event.ydata]
if sign(event.step):
self.zoom(1.5, center=center)
else:
self.zoom(1/1.5, center=center)
def on_window_scroll(self, event):
print "Scroll"
def on_key_over_plot(self, event):
print 'you pressed', event.key, event.xdata, event.ydata
if event.key == '1': # 1
self.on_zoom_fit(None)
return
if val == 50: # 2
if event.key == '2': # 2
self.zoom(1/1.5, self.mouse)
return
if val == 51: # 3
if event.key == '3': # 3
self.zoom(1.5, self.mouse)
return

View File

@ -11,12 +11,16 @@
<property name="can_focus">False</property>
<property name="stock">gtk-open</property>
</object>
<object class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-open</property>
</object>
<object class="GtkWindow" id="window1">
<property name="width_request">600</property>
<property name="height_request">400</property>
<property name="can_focus">False</property>
<signal name="destroy" handler="on_closewindow" swapped="no"/>
<signal name="key-press-event" handler="on_window_key_press" swapped="no"/>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
@ -67,11 +71,12 @@
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem4">
<property name="label">gtk-save-as</property>
<property name="label">Open G-Code</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="image">image3</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_fileopengcode" swapped="no"/>
</object>
</child>
<child>
@ -259,6 +264,23 @@
<property name="margin_right">3</property>
<property name="margin_top">3</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">4</property>
<property name="label" translatable="yes">GERBER</property>
<property name="use_underline">True</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="cb_mergepolys">
<property name="label" translatable="yes">Merge Polygons</property>
@ -272,7 +294,22 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton1">
<property name="label" translatable="yes">Solid</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
@ -287,9 +324,67 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">4</property>
<property name="label" translatable="yes">G-CODE</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Tool dia: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_tooldia">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="text" translatable="yes">0.0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
@ -327,12 +422,6 @@
<object class="GtkGrid" id="grid1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkScrollbar" id="scrollbar1">
<property name="height_request">25</property>
@ -364,6 +453,12 @@
<property name="height">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="resize">True</property>

4
descartes/__init__.py Normal file
View File

@ -0,0 +1,4 @@
"""Turn geometric objects into matplotlib patches"""
from descartes.patch import PolygonPatch

BIN
descartes/__init__.pyc Normal file

Binary file not shown.

66
descartes/patch.py Normal file
View File

@ -0,0 +1,66 @@
"""Paths and patches"""
from matplotlib.patches import PathPatch
from matplotlib.path import Path
from numpy import asarray, concatenate, ones
class Polygon(object):
# Adapt Shapely or GeoJSON/geo_interface polygons to a common interface
def __init__(self, context):
if hasattr(context, 'interiors'):
self.context = context
else:
self.context = getattr(context, '__geo_interface__', context)
@property
def geom_type(self):
return (getattr(self.context, 'geom_type', None)
or self.context['type'])
@property
def exterior(self):
return (getattr(self.context, 'exterior', None)
or self.context['coordinates'][0])
@property
def interiors(self):
value = getattr(self.context, 'interiors', None)
if value is None:
value = self.context['coordinates'][1:]
return value
def PolygonPath(polygon):
"""Constructs a compound matplotlib path from a Shapely or GeoJSON-like
geometric object"""
this = Polygon(polygon)
assert this.geom_type == 'Polygon'
def coding(ob):
# The codes will be all "LINETO" commands, except for "MOVETO"s at the
# beginning of each subpath
n = len(getattr(ob, 'coords', None) or ob)
vals = ones(n, dtype=Path.code_type) * Path.LINETO
vals[0] = Path.MOVETO
return vals
vertices = concatenate(
[asarray(this.exterior)]
+ [asarray(r) for r in this.interiors])
codes = concatenate(
[coding(this.exterior)]
+ [coding(r) for r in this.interiors])
return Path(vertices, codes)
def PolygonPatch(polygon, **kwargs):
"""Constructs a matplotlib patch from a geometric object
The `polygon` may be a Shapely or GeoJSON-like object with or without holes.
The `kwargs` are those supported by the matplotlib.patches.Polygon class
constructor. Returns an instance of matplotlib.patches.PathPatch.
Example (using Shapely Point and a matplotlib axes):
>>> b = Point(0, 0).buffer(1.0)
>>> patch = PolygonPatch(b, fc='blue', ec='blue', alpha=0.5)
>>> axis.add_patch(patch)
"""
return PathPatch(PolygonPath(polygon), **kwargs)

BIN
descartes/patch.pyc Normal file

Binary file not shown.

38
descartes/tests.py Normal file
View File

@ -0,0 +1,38 @@
from shapely.geometry import *
import unittest
from descartes.patch import PolygonPatch
class PolygonTestCase(unittest.TestCase):
polygon = Point(0, 0).buffer(10.0).difference(
MultiPoint([(-5, 0), (5, 0)]).buffer(3.0))
def test_patch(self):
patch = PolygonPatch(self.polygon)
self.failUnlessEqual(str(type(patch)),
"<class 'matplotlib.patches.PathPatch'>")
path = patch.get_path()
self.failUnless(len(path.vertices) == len(path.codes) == 198)
class JSONPolygonTestCase(unittest.TestCase):
polygon = Point(0, 0).buffer(10.0).difference(
MultiPoint([(-5, 0), (5, 0)]).buffer(3.0))
def test_patch(self):
geo = self.polygon.__geo_interface__
patch = PolygonPatch(geo)
self.failUnlessEqual(str(type(patch)),
"<class 'matplotlib.patches.PathPatch'>")
path = patch.get_path()
self.failUnless(len(path.vertices) == len(path.codes) == 198)
class GeoInterfacePolygonTestCase(unittest.TestCase):
class GeoThing:
__geo_interface__ = None
thing = GeoThing()
thing.__geo_interface__ = Point(0, 0).buffer(10.0).difference(
MultiPoint([(-5, 0), (5, 0)]).buffer(3.0)).__geo_interface__
def test_patch(self):
patch = PolygonPatch(self.thing)
self.failUnlessEqual(str(type(patch)),
"<class 'matplotlib.patches.PathPatch'>")
path = patch.get_path()
self.failUnless(len(path.vertices) == len(path.codes) == 198)