Fixed g-code arc parse/plot
This commit is contained in:
parent
3cb9e444c0
commit
145496b4ae
402
camlib.py
402
camlib.py
|
@ -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):
|
||||
'''
|
||||
|
|
BIN
camlib.pyc
BIN
camlib.pyc
Binary file not shown.
340
cirkuix.py
340
cirkuix.py
|
@ -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
|
||||
|
||||
|
|
119
cirkuix.ui
119
cirkuix.ui
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
"""Turn geometric objects into matplotlib patches"""
|
||||
|
||||
from descartes.patch import PolygonPatch
|
||||
|
Binary file not shown.
|
@ -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)
|
Binary file not shown.
|
@ -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)
|
Loading…
Reference in New Issue