From 3cb9e444c01482d60231d6331d8fbb0a5578d600 Mon Sep 17 00:00:00 2001 From: Juan Pablo Caram Date: Mon, 6 Jan 2014 22:08:55 -0500 Subject: [PATCH] Zoom and Excellon plot --- camlib.py | 1758 +--------------------------------------------------- camlib.pyc | Bin 54173 -> 18832 bytes cirkuix.py | 264 ++++++-- cirkuix.ui | 197 +++--- 4 files changed, 329 insertions(+), 1890 deletions(-) diff --git a/camlib.py b/camlib.py index f231ffbc..83e3e70b 100644 --- a/camlib.py +++ b/camlib.py @@ -107,6 +107,9 @@ class Gerber (Geometry): # Flashes [{'loc':[float,float], 'aperture':dict}] self.flashes = [] + # Geometry from flashes + self.flash_geometry = [] + def fix_regions(self): ''' Overwrites the region polygons with fixed @@ -227,17 +230,14 @@ class Gerber (Geometry): # EOF, create shapely LineString if something in path self.paths.append({"linestring":LineString(path), "aperture":last_path_aperture}) - - def create_geometry(self): - if len(self.buffered_paths) == 0: - self.buffer_paths() - self.fix_regions() - flash_polys = [] + + def do_flashes(self): + self.flash_geometry = [] for flash in self.flashes: aperture = self.apertures[flash['aperture']] if aperture['type'] == 'C': # Circles circle = Point(flash['loc']).buffer(aperture['size']/2) - flash_polys.append(circle) + self.flash_geometry.append(circle) continue if aperture['type'] == 'R': # Rectangles loc = flash['loc'] @@ -248,14 +248,20 @@ class Gerber (Geometry): miny = loc[1] - height/2 maxy = loc[1] + height/2 rectangle = shply_box(minx, miny, maxx, maxy) - flash_polys.append(rectangle) + self.flash_geometry.append(rectangle) continue - print "WARNING: Aperture type %s not implemented"%(aperture['type']) #TODO: Add support for type='O' + print "WARNING: Aperture type %s not implemented"%(aperture['type']) + + def create_geometry(self): + if len(self.buffered_paths) == 0: + self.buffer_paths() + self.fix_regions() + self.do_flashes() self.solid_geometry = cascaded_union( self.buffered_paths + [poly['polygon'] for poly in self.regions] + - flash_polys) + self.flash_geometry) class CNCjob: def __init__(self, units="in", kind="generic", z_move = 0.1, @@ -546,75 +552,8 @@ class Excellon(Geometry): for drill in self.drills: poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0) self.solid_geometry.append(poly) - -def fix_poly(poly): - ''' - Fixes polygons with internal cutouts by identifying - loops and touching segments. Loops are extracted - as individual polygons. If a smaller loop is still - not a valid Polygon, fix_poly2() adds vertices such - that fix_poly() can continue to extract smaller loops. - ''' - if poly.is_valid: # Nothing to do - return [poly] - - coords = poly.exterior.coords[:] - n_points = len(coords) - i = 0 - result = [] - while i1: # points do not repeat in 1 step - sub_poly = Polygon(coords[j:i+1]) - if sub_poly.is_valid: - result.append(sub_poly) - elif sub_poly.area > 0: - sub_poly = fix_poly2(sub_poly) - result += fix_poly(sub_poly) # try again - - # Preserve the repeated point such as not to break the - # remaining geometry - remaining = coords[:j+1]+coords[i+1:n_points+1] - rem_poly = Polygon(remaining) - if len(remaining)>2 and rem_poly.area > 0: - result += fix_poly(rem_poly) - break - i += 1 - return result - -def fix_poly2(poly): - coords = poly.exterior.coords[:] - n_points = len(coords) - ls = None - i = 1 - while i xmax: - xmax = mot.end[X] - if mot.end[X] > xmax: - xmax = mot.end[X] - if mot.start[Y] < ymin: - ymin = mot.start[Y] - if mot.end[Y] < ymin: - ymin = mot.end[Y] - if mot.start[Y] > ymax: - ymax = mot.end[Y] - if mot.end[Y] > ymax: - ymax = mot.end[Y] - width = xmax - xmin - height = ymax - ymin - print "x in", xmin, xmax - print "y in", ymin, ymax - print "width", width - print "heigh", height - - # Create surface if it doesn't exist - if self.surface == None: - self.pixel_width = int(width*self.resolution + 2*margin) - self.pixel_height = int(height*self.resolution + 2*margin) - self.create_surface(self.pixel_width, self.pixel_height) - self.origin = [int(-xmin*self.resolution + margin), - int(-ymin*self.resolution + margin)] - print "Created surface: %d x %d"%(self.pixel_width, self.pixel_height) - print "Origin: %d, %d"%(self.origin[X], self.origin[Y]) - - # Context - # Flip and shift - self.context = cairo.Context(self.surface) - cr = self.context - - cr.set_source_rgb(0.9, 0.9, 0.9) - cr.rectangle(0,0, self.pixel_width, self.pixel_height) - cr.fill() - - cr.scale(self.resolution, -self.resolution) - cr.translate(self.origin[X]/self.resolution, - (-self.pixel_height+self.origin[Y])/self.resolution) - - # Draw - cr.move_to(0,0) - cr.set_line_width (linewidth) - cr.set_line_cap(cairo.LINE_CAP_ROUND) - n = len(motions) - for i in range(0, n): - - - #if motions[i].depth<0 and i>0 and motions[i-1].depth>0: - if motions[i].depth <= 0: - # change to cutting - #print "x", - # Draw previous travel - cr.set_source_rgba (0.3, 0.2, 0.1, 1.0) # Solid color - #cr.stroke() - #if motions[i].depth >0 and i>0 and motions[i-1].depth<0: - if motions[i].depth > 0: - # change to cutting - #print "-", - # Draw previous cut - cr.set_source_rgba (0.3, 0.2, 0.5, 0.2) - #cr.stroke() - - if motions[i].typ == 'line': - cr.move_to(motions[i].start[0], motions[i].start[1]) - cr.line_to(motions[i].end[0], motions[i].end[1]) - cr.stroke() - #print 'cr.line_to(%f, %f)'%(motions[i].end[0], motions[i].end[0]), - - if motions[i].typ == 'arcacw': - c = (motions[i].offset[0]+motions[i].start[0], - motions[i].offset[1]+motions[i].start[1]) - r = sqrt(motions[i].offset[0]**2 + motions[i].offset[1]**2) - ts = arctan2(-motions[i].offset[1], -motions[i].offset[0]) - te = arctan2(-c[1]+motions[i].end[1], -c[0]+motions[i].end[0]) - if te <= ts: - te += 2*pi - cr.arc(c[0], c[1], r, ts, te) - cr.stroke() - - if motions[i].typ == 'arccw': - c = (motions[i].offset[0]+motions[i].start[0], - motions[i].offset[1]+motions[i].start[1]) - r = sqrt(motions[i].offset[0]**2 + motions[i].offset[1]**2) - ts = arctan2(-motions[i].offset[1], -motions[i].offset[0]) - te = arctan2(-c[1]+motions[i].end[1], -c[0]+motions[i].end[0]) - if te <= ts: - te += 2*pi - cr.arc(c[0], c[1], r, te, ts) - cr.stroke() - - def draw_boundaries(self, boundaries, linewidth, margin=10): - ''' - margin Margin in pixels. - ''' - # Analyze boundaries - X = 0 - Y = 1 - #Z = 2 - xmin = Inf - xmax = -Inf - ymin = Inf - ymax = -Inf - for seg in boundaries[0]: - for vertex in seg: - try: - if vertex[X] < xmin: - xmin = vertex[X] - if vertex[X] > xmax: - xmax = vertex[X] - if vertex[Y] < ymin: - ymin = vertex[Y] - if vertex[Y] > ymax: - ymax = vertex[Y] - except: - print "Woops! vertex = [", [x for x in vertex], "]" - width = xmax - xmin - height = ymax - ymin - print "x in", xmin, xmax - print "y in", ymin, ymax - print "width", width - print "heigh", height - - # Create surface if it doesn't exist - if self.surface == None: - self.pixel_width = int(width*self.resolution + 2*margin) - self.pixel_height = int(height*self.resolution + 2*margin) - self.create_surface(self.pixel_width, self.pixel_height) - self.origin = [int(-xmin*self.resolution + margin), - int(-ymin*self.resolution + margin)] - print "Created surface: %d x %d"%(self.pixel_width, self.pixel_height) - print "Origin: %d, %d"%(self.origin[X], self.origin[Y]) - - # Context - # Flip and shift - self.context = cairo.Context(self.surface) - cr = self.context - - cr.set_source_rgb(0.9, 0.9, 0.9) - cr.rectangle(0,0, self.pixel_width, self.pixel_height) - cr.fill() - - cr.scale(self.resolution, -self.resolution) - cr.translate(self.origin[X]/self.resolution, - (-self.pixel_height+self.origin[Y])/self.resolution) - - # Draw - - cr.set_line_width (linewidth) - cr.set_line_cap(cairo.LINE_CAP_ROUND) - cr.set_source_rgba (0.3, 0.2, 0.5, 1) - for seg in boundaries[0]: - #print "segment" - cr.move_to(seg[0][X],seg[0][Y]) - for i in range(1,len(seg)): - #print seg[i][X],seg[i][Y] - cr.line_to(seg[i][X], seg[i][Y]) - cr.stroke() - -def plotby(b, res, linewidth, ims=None): - ''' - Creates a Cairo image object for the "boundarys" object - generated by read_gerber(). - ''' - X = 0 - Y = 1 - xmin = Inf - xmax = -Inf - ymin = Inf - ymax = -Inf - for seg in b[0]: - for vertex in seg: - try: - if vertex[X] < xmin: - xmin = vertex[X] - if vertex[X] > xmax: - xmax = vertex[X] - if vertex[Y] < ymin: - ymin = vertex[Y] - if vertex[Y] > ymax: - ymax = vertex[Y] - except: - print "Woops! vertex = [", [x for x in vertex], "]" - - width = xmax - xmin - height = ymax - ymin - print "x in", xmin, xmax - print "y in", ymin, ymax - print "width", width - print "heigh", height - - WIDTH = int((xmax-xmin)*res) - HEIGHT = int((ymax-ymin)*res) - # Create a new image if none given - if ims == None: - ims = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) - cr = cairo.Context(ims) - cr.scale(res, -res) - #cr.scale(res, res) - #cr.translate(0, -(ymax-ymin)) - #cr.translate(-xmin, -ymin) - cr.translate(-xmin, -(ymax)) - cr.set_line_width (linewidth) - cr.set_line_cap(cairo.LINE_CAP_ROUND) - cr.set_source_rgba (0.3, 0.2, 0.5, 1) - - - - - for seg in b[0]: - #print "segment" - cr.move_to(seg[0][X],seg[0][Y]) - for i in range(1,len(seg)): - #print seg[i][X],seg[i][Y] - cr.line_to(seg[i][X], seg[i][Y]) - cr.stroke() - - - cr.scale(1,-1) - cr.translate(-xmin, -(ymax)) - cr.set_source_rgba (1, 0, 0, 1) - cr.select_font_face("Arial", cairo.FONT_SLANT_NORMAL, - cairo.FONT_WEIGHT_NORMAL) - cr.set_font_size(0.1) - cr.move_to(0, 0) - cr.show_text("(0,0)") - cr.move_to(1, 1) - cr.show_text("(1,1)") - - return ims - ############### cam.py #################### def coord(gstr,digits,fraction): ''' @@ -1108,1397 +776,5 @@ def coord(gstr,digits,fraction): gerby = y return [x,y] -def read_Gerber_Shapely(filename, nverts=10): - ''' - Gerber parser. - ''' - EPS = 1e-20 - TYPE = 0 - SIZE = 1 - WIDTH = 1 - HEIGHT = 2 - - gfile = open(filename, 'r') - gstr = gfile.readlines() - gfile.close() - segment = -1 - xold = [] - yold = [] - boundary = [] - macros = [] - N_macros = 0 - apertures = [[] for i in range(1000)] - for gline in gstr: - if (find(gline, "%FS") != -1): - ### format statement ### - index = find(gline, "X") - digits = int(gline[index + 1]) - fraction = int(gline[index + 2]) - continue - elif (find(gline, "%AM") != -1): - ### aperture macro ### - index = find(gline, "%AM") - index1 = find(gline, "*") - macros.append([]) - macros[-1] = gline[index + 3:index1] - N_macros += 1 - continue - elif (find(gline, "%MOIN*%") != -1): - # inches - continue - elif (find(gline, "G01*") != -1): - ### linear interpolation ### - continue - elif (find(gline, "G70*") != -1): - ### inches ### - continue - elif (find(gline, "G75*") != -1): - ### circular interpolation ### - continue - elif (find(gline, "%ADD") != -1): - ### aperture definition ### - index = find(gline, "%ADD") - parse = 0 - if (find(gline, "C,") != -1): - ## circle ## - index = find(gline, "C,") - index1 = find(gline, "*") - aperture = int(gline[4:index]) - size = float(gline[index + 2:index1]) - apertures[aperture] = ["C", size] - print " read aperture", aperture, ": circle diameter", size - continue - elif (find(gline, "O,") != -1): - ## obround ## - index = find(gline, "O,") - aperture = int(gline[4:index]) - index1 = find(gline, ",", index) - index2 = find(gline, "X", index) - index3 = find(gline, "*", index) - width = float(gline[index1 + 1:index2]) - height = float(gline[index2 + 1:index3]) - apertures[aperture] = ["O", width, height] - print " read aperture", aperture, ": obround", width, "x", height - continue - elif (find(gline, "R,") != -1): - ## rectangle ## - index = find(gline, "R,") - aperture = int(gline[4:index]) - index1 = find(gline, ",", index) - index2 = find(gline, "X", index) - index3 = find(gline, "*", index) - width = float(gline[index1 + 1:index2]) - height = float(gline[index2 + 1:index3]) - apertures[aperture] = ["R", width, height] - print " read aperture", aperture, ": rectangle", width, "x", height - continue - for macro in range(N_macros): - ## macros ## - index = find(gline, macros[macro] + ',') - if (index != -1): - # hack: assume macros can be approximated by - # a circle, and has a size parameter - aperture = int(gline[4:index]) - index1 = find(gline, ",", index) - index2 = find(gline, "*", index) - size = float(gline[index1 + 1:index2]) - apertures[aperture] = ["C", size] - print " read aperture", aperture, ": macro (assuming circle) diameter", size - parse = 1 - continue - if (parse == 0): - print " aperture not implemented:", gline - return - # End of if aperture definition - elif (find(gline, "D01*") != -1): - ### pen down ### - [xnew, ynew] = coord(gline, digits, fraction) - if (size > EPS): - if ((abs(xnew - xold) > EPS) | (abs(ynew - yold) > EPS)): - newpath = stroke(xold, yold, xnew, ynew, size, nverts=nverts) - boundary.append(newpath) - segment += 1 - else: - boundary[segment].append([xnew, ynew, []]) - xold = xnew - yold = ynew - continue - elif (find(gline, "D02*") != -1): - ### pen up ### - [xold, yold] = coord(gline, digits, fraction) - if (size < EPS): - boundary.append([]) - segment += 1 - boundary[segment].append([xold, yold, []]) - newpath = [] - continue - elif (find(gline, "D03*") != -1): - ### flash ### - if (find(gline, "D03*") == 0): - # coordinates on preceeding line - [xnew, ynew] = [xold, yold] - else: - # coordinates on this line - [xnew, ynew] = coord(gline, digits, fraction) - if (apertures[aperture][TYPE] == "C"): - # circle - boundary.append([]) - segment += 1 - size = apertures[aperture][SIZE] - for i in range(nverts): - angle = i * 2.0 * pi / (nverts - 1.0) - x = xnew + (size / 2.0) * cos(angle) - y = ynew + (size / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - elif (apertures[aperture][TYPE] == "R"): - # rectangle - boundary.append([]) - segment += 1 - width = apertures[aperture][WIDTH] / 2.0 - height = apertures[aperture][HEIGHT] / 2.0 - boundary[segment].append([xnew - width, ynew - height, []]) - boundary[segment].append([xnew + width, ynew - height, []]) - boundary[segment].append([xnew + width, ynew + height, []]) - boundary[segment].append([xnew - width, ynew + height, []]) - boundary[segment].append([xnew - width, ynew - height, []]) - elif (apertures[aperture][TYPE] == "O"): - # obround - boundary.append([]) - segment += 1 - width = apertures[aperture][WIDTH] - height = apertures[aperture][HEIGHT] - if (width > height): - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) + pi / 2.0 - x = xnew - (width - height) / 2.0 + (height / 2.0) * cos(angle) - y = ynew + (height / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) - pi / 2.0 - x = xnew + (width - height) / 2.0 + (height / 2.0) * cos(angle) - y = ynew + (height / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - else: - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) + pi - x = xnew + (width / 2.0) * cos(angle) - y = ynew - (height - width) / 2.0 + (width / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) - x = xnew + (width / 2.0) * cos(angle) - y = ynew + (height - width) / 2.0 + (width / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - boundary[segment].append(boundary[segment][0]) - else: - print " aperture", apertures[aperture][TYPE], "is not implemented" - return - xold = xnew - yold = ynew - continue # End of flash - elif (find(gline, "D") == 0): - ### change aperture ### - index = find(gline, '*') - aperture = int(gline[1:index]) - size = apertures[aperture][SIZE] - continue - elif (find(gline, "G54D") == 0): - ### change aperture ### - index = find(gline, '*') - aperture = int(gline[4:index]) - size = apertures[aperture][SIZE] - continue - else: - print " not parsed:", gline - boundarys[0] = boundary - - -def read_Gerber(filename, nverts=10): - ''' - Gerber parser. - ''' - global boundarys - EPS = 1e-20 - TYPE = 0 - SIZE = 1 - WIDTH = 1 - HEIGHT = 2 - - gfile = open(filename, 'r') - gstr = gfile.readlines() - gfile.close() - segment = -1 - xold = [] - yold = [] - boundary = [] - macros = [] - N_macros = 0 - apertures = [[] for i in range(1000)] - for gline in gstr: - if (find(gline, "%FS") != -1): - ### format statement ### - index = find(gline, "X") - digits = int(gline[index + 1]) - fraction = int(gline[index + 2]) - continue - elif (find(gline, "%AM") != -1): - ### aperture macro ### - index = find(gline, "%AM") - index1 = find(gline, "*") - macros.append([]) - macros[-1] = gline[index + 3:index1] - N_macros += 1 - continue - elif (find(gline, "%MOIN*%") != -1): - # inches - continue - elif (find(gline, "G01*") != -1): - ### linear interpolation ### - continue - elif (find(gline, "G70*") != -1): - ### inches ### - continue - elif (find(gline, "G75*") != -1): - ### circular interpolation ### - continue - elif (find(gline, "%ADD") != -1): - ### aperture definition ### - index = find(gline, "%ADD") - parse = 0 - if (find(gline, "C,") != -1): - ## circle ## - index = find(gline, "C,") - index1 = find(gline, "*") - aperture = int(gline[4:index]) - size = float(gline[index + 2:index1]) - apertures[aperture] = ["C", size] - print " read aperture", aperture, ": circle diameter", size - continue - elif (find(gline, "O,") != -1): - ## obround ## - index = find(gline, "O,") - aperture = int(gline[4:index]) - index1 = find(gline, ",", index) - index2 = find(gline, "X", index) - index3 = find(gline, "*", index) - width = float(gline[index1 + 1:index2]) - height = float(gline[index2 + 1:index3]) - apertures[aperture] = ["O", width, height] - print " read aperture", aperture, ": obround", width, "x", height - continue - elif (find(gline, "R,") != -1): - ## rectangle ## - index = find(gline, "R,") - aperture = int(gline[4:index]) - index1 = find(gline, ",", index) - index2 = find(gline, "X", index) - index3 = find(gline, "*", index) - width = float(gline[index1 + 1:index2]) - height = float(gline[index2 + 1:index3]) - apertures[aperture] = ["R", width, height] - print " read aperture", aperture, ": rectangle", width, "x", height - continue - for macro in range(N_macros): - ## macros ## - index = find(gline, macros[macro] + ',') - if (index != -1): - # hack: assume macros can be approximated by - # a circle, and has a size parameter - aperture = int(gline[4:index]) - index1 = find(gline, ",", index) - index2 = find(gline, "*", index) - size = float(gline[index1 + 1:index2]) - apertures[aperture] = ["C", size] - print " read aperture", aperture, ": macro (assuming circle) diameter", size - parse = 1 - continue - if (parse == 0): - print " aperture not implemented:", gline - return - # End of if aperture definition - elif (find(gline, "D01*") != -1): - ### pen down ### - [xnew, ynew] = coord(gline, digits, fraction) - if (size > EPS): - if ((abs(xnew - xold) > EPS) | (abs(ynew - yold) > EPS)): - newpath = stroke(xold, yold, xnew, ynew, size, nverts=nverts) - boundary.append(newpath) - segment += 1 - else: - boundary[segment].append([xnew, ynew, []]) - xold = xnew - yold = ynew - continue - elif (find(gline, "D02*") != -1): - ### pen up ### - [xold, yold] = coord(gline, digits, fraction) - if (size < EPS): - boundary.append([]) - segment += 1 - boundary[segment].append([xold, yold, []]) - newpath = [] - continue - elif (find(gline, "D03*") != -1): - ### flash ### - if (find(gline, "D03*") == 0): - # coordinates on preceeding line - [xnew, ynew] = [xold, yold] - else: - # coordinates on this line - [xnew, ynew] = coord(gline, digits, fraction) - if (apertures[aperture][TYPE] == "C"): - # circle - boundary.append([]) - segment += 1 - size = apertures[aperture][SIZE] - for i in range(nverts): - angle = i * 2.0 * pi / (nverts - 1.0) - x = xnew + (size / 2.0) * cos(angle) - y = ynew + (size / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - elif (apertures[aperture][TYPE] == "R"): - # rectangle - boundary.append([]) - segment += 1 - width = apertures[aperture][WIDTH] / 2.0 - height = apertures[aperture][HEIGHT] / 2.0 - boundary[segment].append([xnew - width, ynew - height, []]) - boundary[segment].append([xnew + width, ynew - height, []]) - boundary[segment].append([xnew + width, ynew + height, []]) - boundary[segment].append([xnew - width, ynew + height, []]) - boundary[segment].append([xnew - width, ynew - height, []]) - elif (apertures[aperture][TYPE] == "O"): - # obround - boundary.append([]) - segment += 1 - width = apertures[aperture][WIDTH] - height = apertures[aperture][HEIGHT] - if (width > height): - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) + pi / 2.0 - x = xnew - (width - height) / 2.0 + (height / 2.0) * cos(angle) - y = ynew + (height / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) - pi / 2.0 - x = xnew + (width - height) / 2.0 + (height / 2.0) * cos(angle) - y = ynew + (height / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - else: - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) + pi - x = xnew + (width / 2.0) * cos(angle) - y = ynew - (height - width) / 2.0 + (width / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - for i in range(nverts / 2): - angle = i * pi / (nverts / 2 - 1.0) - x = xnew + (width / 2.0) * cos(angle) - y = ynew + (height - width) / 2.0 + (width / 2.0) * sin(angle) - boundary[segment].append([x, y, []]) - boundary[segment].append(boundary[segment][0]) - else: - print " aperture", apertures[aperture][TYPE], "is not implemented" - return - xold = xnew - yold = ynew - continue # End of flash - elif (find(gline, "D") == 0): - ### change aperture ### - index = find(gline, '*') - aperture = int(gline[1:index]) - size = apertures[aperture][SIZE] - continue - elif (find(gline, "G54D") == 0): - ### change aperture ### - index = find(gline, '*') - aperture = int(gline[4:index]) - size = apertures[aperture][SIZE] - continue - else: - print " not parsed:", gline - - boundarys[0] = boundary - -def read_Excellon(filename): - global boundarys - # - # Excellon parser - # - file = open(filename,'r') - str = file.readlines() - file.close() - segment = -1 - line = 0 - nlines = len(str) - boundary = [] - #header = TRUE - drills = [[] for i in range(1000)] - while line < nlines: - if ((find(str[line],"T") != -1) & (find(str[line],"C") != -1) \ - & (find(str[line],"F") != -1)): - # - # alternate drill definition style - # - index = find(str[line],"T") - index1 = find(str[line],"C") - index2 = find(str[line],"F") - drill = int(str[line][1:index1]) - print str[line][index1+1:index2] - size = float(str[line][index1+1:index2]) - drills[drill] = ["C",size] - print " read drill",drill,"size:",size - line += 1 - continue - if ((find(str[line],"T") != -1) & (find(str[line]," ") != -1) \ - & (find(str[line],"in") != -1)): - # - # alternate drill definition style - # - index = find(str[line],"T") - index1 = find(str[line]," ") - index2 = find(str[line],"in") - drill = int(str[line][1:index1]) - print str[line][index1+1:index2] - size = float(str[line][index1+1:index2]) - drills[drill] = ["C",size] - print " read drill",drill,"size:",size - line += 1 - continue - elif ((find(str[line],"T") != -1) & (find(str[line],"C") != -1)): - # - # alternate drill definition style - # - index = find(str[line],"T") - index1 = find(str[line],"C") - drill = int(str[line][1:index1]) - size = float(str[line][index1+1:-1]) - drills[drill] = ["C",size] - print " read drill",drill,"size:",size - line += 1 - continue - elif (find(str[line],"T") == 0): - # - # change drill - # - index = find(str[line],'T') - drill = int(str[line][index+1:-1]) - size = drills[drill][SIZE] - line += 1 - continue - elif (find(str[line],"X") != -1): - # - # drill location - # - index = find(str[line],"X") - index1 = find(str[line],"Y") - x0 = float(int(str[line][index+1:index1])/10000.0) - y0 = float(int(str[line][index1+1:-1])/10000.0) - line += 1 - boundary.append([]) - segment += 1 - size = drills[drill][SIZE] - for i in range(nverts): - angle = -i*2.0*pi/(nverts-1.0) - x = x0 + (size/2.0)*cos(angle) - y = y0 + (size/2.0)*sin(angle) - boundary[segment].append([x,y,[]]) - continue - else: - print " not parsed:",str[line] - line += 1 - boundarys[0] = boundary - -def stroke(x0,y0,x1,y1,width, nverts=10): - # - # stroke segment with width - # - #print "stroke:",x0,y0,x1,y1,width - X = 0 - Y = 1 - dx = x1 - x0 - dy = y1 - y0 - d = sqrt(dx*dx + dy*dy) - dxpar = dx / d - dypar = dy / d - dxperp = dypar - dyperp = -dxpar - dx = -dxperp * width/2.0 - dy = -dyperp * width/2.0 - angle = pi/(nverts/2-1.0) - c = cos(angle) - s = sin(angle) - newpath = [] - for i in range(nverts/2): - newpath.append([x0+dx,y0+dy,0]) - [dx,dy] = [c*dx-s*dy, s*dx+c*dy] - dx = dxperp * width/2.0 - dy = dyperp * width/2.0 - for i in range(nverts/2): - newpath.append([x1+dx,y1+dy,0]) - [dx,dy] = [c*dx-s*dy, s*dx+c*dy] - x0 = newpath[0][X] - y0 = newpath[0][Y] - newpath.append([x0,y0,0]) - return newpath - -def contour(event): - ''' - Uses displace() and adjust_contours() - ''' - global boundarys, toolpaths, contours - # - # contour boundary to find toolpath - # - print "contouring boundary ..." - xyscale = float(sxyscale.get()) - undercut = float(sundercut.get()) - if (undercut != 0.0): - print " undercutting contour by",undercut - N_contour = 1 - if (len(boundarys) == 1): - # - # 2D contour - # - toolpaths[0] = [] - for n in range(N_contour): - toolrad = (n+1)*(float(sdia.get())/2.0-undercut)/xyscale - contours[0] = displace(boundarys[0],toolrad) - altern = ialtern.get(); - if (altern == TRUE): - contours[0] = adjust_contour(contours[0],boundarys[0],toolrad) - else: - contours[0] = prune(contours[0],-1,event) - toolpaths[0].extend(contours[0]) - plot(event) - else: - # - # 3D contour - # - for layer in range(len(boundarys)): - toolpaths[layer] = [] - contours[layer] = [] - if (boundarys[layer] != []): - [xindex,yindex,zindex,z] = orient(boundarys[layer]) - for n in range(N_contour): - toolrad = (n+1)*(float(sdia.get())/2.0-undercut)/xyscale - path = project(boundarys[layer],xindex,yindex) - contour = displace(path,toolrad) - contour = prune(contour,-1,event) - contours[layer] = lift(contour,xindex,yindex,zindex,z) - toolpaths[layer].extend(contours[layer]) - plot(event) - print " done" - -def adjust_contour(path, boundary, toolrad): - print " adjust_contour ..." - newpath = [] - for seg in range(len(path)): - newpath.append([]) -# print "points" -# for vert in range(len(path[seg])): -# Px = boundary[seg][vert][X] -# Py = boundary[seg][vert][Y] -# print "%2i : %5.2f,%5.2f" % (vert, Px, Py) - -# print "len(path[seg]): ", len(path[seg]) -# print "len(boundary[seg]: ", len(boundary[seg]) - for vert in range(len(path[seg])): - Px = path[seg][vert][X] - Py = path[seg][vert][Y] - avgvalue = [] - avgvalue.append(0.0) - avgvalue.append(0.0) - changed = 1 - iteration = 0 - avg = [] - while ((iteration < MAXITER) & (changed != 0)): - changed = 0 - - for orgvert in range(len(boundary[seg]) - 1): -# if (orgvert == 0): -# x0 = boundary[seg][len(boundary[seg]) - 1][X] -# y0 = boundary[seg][len(boundary[seg]) - 1][Y] -# else: - x0 = boundary[seg][orgvert][X] - y0 = boundary[seg][orgvert][Y] - - x1 = boundary[seg][orgvert + 1][X] - y1 = boundary[seg][orgvert + 1][Y] - - #print ' A %5.2f,%5.2f B %5.2f,%5.2f' % (x0, y0, x1, y1) - - dx = x1 - x0 - dy = y1 - y0 - - nx = dy; - ny = -dx; - - d = abs(((nx * Px + ny * Py) - (nx * x0 + ny * y0) ) / \ - sqrt( nx * nx + ny * ny )) - - pre = orgvert - 1 - - if (pre < 0): - pre = len(boundary[seg]) - 2 - post = orgvert + 2 - if (post == len(boundary[seg])): - post = 1 - - - #print " distance %5.2f" % d - #print "toolrad ", toolrad - if (d - toolrad < - NOISE): -# if (x0 < 1000000000): - #print " low distance" - # check if inside - pre = orgvert - 1 - if (pre < 0): - pre = len(boundary[seg]) - 2 - post = orgvert + 2 - if (post == len(boundary[seg])): - post = 1 - - diff_d_pre_x = x1 - boundary[seg][pre][X] - diff_d_pre_y = y1 - boundary[seg][pre][Y] - diff_d_post_x = boundary[seg][post][X] - x0 - diff_d_post_y = boundary[seg][post][Y] - y0 - - #print "diff_pre %5.2f,%5.2f" % (diff_d_pre_x, diff_d_pre_y) - #print "diff_post %5.2f,%5.2f" % (diff_d_post_x, diff_d_post_y) - - #n_pre_x = diff_d_pre_y - #n_pre_y = -diff_d_pre_x - #n_post_x = diff_d_post_y - #n_post_y = -diff_d_post_x - - - diff_px0 = Px - x0 - diff_py0 = Py - y0 - diff_px1 = Px - x1 - diff_py1 = Py - y1 - - #print "diff p0 %5.2f,%5.2f" % (diff_px0, diff_py0) - #print "diff p1 %5.2f,%5.2f" % (diff_px1, diff_py1) - - pre_x = boundary[seg][pre][X] - pre_y = boundary[seg][pre][Y] - post_x = boundary[seg][post][X] - post_y = boundary[seg][post][Y] - - v0_x = x0 - pre_x - v0_y = y0 - pre_y - - v1_x = post_x - x0 - v1_y = post_y - y0 - - - if ((v0_x * nx + v0_y * ny) > -NOISE): #angle > 180 - #print "XXXXXXXXXXXXXXXXXXX pre > 180" - value0 = diff_d_pre_x * diff_px0 + diff_d_pre_y * diff_py0 - #value0 = diff_px0 * dx + diff_py0 * dy - else: - value0 = diff_px0 * dx + diff_py0 * dy - - if (-(v1_x * nx + v1_y * ny) > -NOISE): #angle > 180 - #print "XXXXXXXXXXXXXXXXXXX post > 180" - value1 = diff_d_post_x * diff_px1 + diff_d_post_y * diff_py1 - #value1 = diff_px1 * dx + diff_py1 * dy - else: - value1 = diff_px1 * dx + diff_py1 * dy - - #if ((value0 > -NOISE) & (value1 < NOISE)): - #print " P %5.2f,%5.2f a %5.2f,%5.2f b %5.2f,%5.2f - inside (%8.5f & %8.5f)" % (Px, Py, x0, y0, x1, y1, value0, value1) - #else: - #print " P %5.2f,%5.2f a %5.2f,%5.2f b %5.2f,%5.2f - outside (%8.5f & %8.5f)" % (Px, Py, x0, y0, x1, y1, value0, value1) - -# if (vert == 3) & (orgvert == 2): -# print "-p1 %5.2f,%5.2f p2 %5.2f,%5.2f P %5.2f,%5.2f " % (x0, y0, x1, y1, Px, Py) -# print "d %5.2f,%5.2f" % (dx, dy) -# print "n %5.2f,%5.2f" % (nx, ny) -# print "di0 %5.2f,%5.2f" % (diff_px0, diff_py0) -# print "di1 %5.2f,%5.2f" % (diff_px1, diff_py1) -# print "val %5.2f,%5.2f" % (value0, value1) - - -# if ((value0 == 0) | (value1 == 0)): -# #print " fix me" -# value = value1 -# else: - if ((value0 > -NOISE) & (value1 < NOISE)): - #value = value1 * value0; - #if (value < 0 ): - #print 'P %5.2f,%5.2f' % (Px, Py) - #print ' A %5.2f,%5.2f B %5.2f,%5.2f' % (x0, y0, x1, y1) - #print " distance %5.2f" % d - #print " move" - ln = sqrt((nx * nx) + (ny * ny)) - Px = Px + (nx / ln) * (toolrad - d); - Py = Py + (ny / ln) * (toolrad - d); - changed += 1 - iteration += 1 -# print ' new %5.2f,%5.2f' % (Px, Py) - if (iteration > MAXITER - AVGITER): -# print "ii %2i %7.4f,%7.4f" % (iteration, Px,Py) - avgvalue[X] += Px - avgvalue[Y] += Py -# if (iteration > 1): -# print iteration - if (iteration >= MAXITER): -# print " diff", (iteration - (MAXITER - AVGITER)) - avgvalue[X] /= float(iteration - (MAXITER - AVGITER)) - avgvalue[Y] /= float(iteration - (MAXITER - AVGITER)) - newpath[seg].append([avgvalue[X],avgvalue[Y],[]]) -# print "NEW : %7.4f,%7.4f" % (avgvalue[X], avgvalue[Y]) - else: - newpath[seg].append([Px,Py,[]]) - -# for vert in range(len(path[seg])): -# Px = newpath[seg][vert][X] -# Py = newpath[seg][vert][Y] -# print "NEW %2i : %5.2f,%5.2f" % (vert, Px, Py) - - return newpath - -def displace(path,toolrad): - ''' - Uses offset() - ''' - # - # displace path inwards by tool radius - # - print " displacing ..." - newpath = [] - for seg in range(len(path)): - newpath.append([]) - if (len(path[seg]) > 2): - for vert1 in range(len(path[seg])-1): - if (vert1 == 0): - vert0 = len(path[seg]) - 2 - else: - vert0 = vert1 - 1 - vert2 = vert1 + 1 - x0 = path[seg][vert0][X] - x1 = path[seg][vert1][X] - x2 = path[seg][vert2][X] - y0 = path[seg][vert0][Y] - y1 = path[seg][vert1][Y] - y2 = path[seg][vert2][Y] - [dx,dy] = offset(x0,x1,x2,y0,y1,y2,toolrad) - if (dx != []): - newpath[seg].append([(x1+dx),(y1+dy),[]]) - x0 = newpath[seg][0][X] - y0 = newpath[seg][0][Y] - newpath[seg].append([x0,y0,[]]) - elif (len(path[seg]) == 2): - x0 = path[seg][0][X] - y0 = path[seg][0][Y] - x1 = path[seg][1][X] - y1 = path[seg][1][Y] - x2 = 2*x1 - x0 - y2 = 2*y1 - y0 - [dx,dy] = offset(x0,x1,x2,y0,y1,y2,toolrad) - if (dx != []): - newpath[seg].append([x0+dx,y0+dy,[]]) - newpath[seg].append([x1+dx,y1+dy,[]]) - else: - newpath[seg].append([x0,y0,[]]) - newpath[seg].append([x1,y1,[]]) - else: - print " displace: shouldn't happen" - return newpath - -def offset(x0,x1,x2,y0,y1,y2,r): - # - # calculate offset by r for vertex 1 - # - dx0 = x1 - x0 - dx1 = x2 - x1 - dy0 = y1 - y0 - dy1 = y2 - y1 - d0 = sqrt(dx0*dx0 + dy0*dy0) - d1 = sqrt(dx1*dx1 + dy1*dy1) - if ((d0 == 0) | (d1 == 0)): - return [[],[]] - dx0par = dx0 / d0 - dy0par = dy0 / d0 - dx0perp = dy0 / d0 - dy0perp = -dx0 / d0 - dx1perp = dy1 / d1 - dy1perp = -dx1 / d1 - #print "offset points:",x0,x1,x2,y0,y1,y2 - #print "offset normals:",dx0perp,dx1perp,dy0perp,dy1perp - if ((abs(dx0perp*dy1perp - dx1perp*dy0perp) < EPS) | \ - (abs(dy0perp*dx1perp - dy1perp*dx0perp) < EPS)): - dx = r * dx1perp - dy = r * dy1perp - #print " offset planar:",dx,dy - elif ((abs(dx0perp+dx1perp) < EPS) & (abs(dy0perp+dy1perp) < EPS)): - dx = r * dx1par - dy = r * dy1par - #print " offset hairpin:",dx,dy - else: - dx = r*(dy1perp - dy0perp) / \ - (dx0perp*dy1perp - dx1perp*dy0perp) - dy = r*(dx1perp - dx0perp) / \ - (dy0perp*dx1perp - dy1perp*dx0perp) - #print " offset OK:",dx,dy - return [dx,dy] - -def prune(path,sign,event): - ''' - Uses add_intersections() and union() - ''' - # - # prune path intersections - # - # first find the intersections - # - print " intersecting ..." - [path, intersections, seg_intersections] = add_intersections(path) - #print 'path:',path - #print 'intersections:',intersections - #print 'seg_intersections:',seg_intersections - # - # then copy non-intersecting segments to new path - # - newpath = [] - for seg in range(len(seg_intersections)): - #print "non-int" - if (seg_intersections[seg] == []): - newpath.append(path[seg]) - # - # finally follow and remove the intersections - # - print " pruning ..." - i = 0 - newseg = 0 - while (i < len(intersections)): - if (intersections[i] == []): - # - # skip null intersections - # - i += 1 - #print "null" - else: - istart = i - intersection = istart - # - # skip interior intersections - # - oldseg = -1 - interior = TRUE - while 1: - #print 'testing intersection',intersection,':',intersections[intersection] - if (intersections[intersection] == []): - #seg == oldseg - seg = oldseg - else: - [seg,vert] = union(intersection,path,intersections,sign) - #print ' seg',seg,'vert',vert,'oldseg',oldseg - if (seg == oldseg): - #print " remove interior intersection",istart - seg0 = intersections[istart][0][SEG] - vert0 = intersections[istart][0][VERT] - path[seg0][vert0][INTERSECT] = -1 - seg1 = intersections[istart][1][SEG] - vert1 = intersections[istart][1][VERT] - path[seg1][vert1][INTERSECT] = -1 - intersections[istart] = [] - break - elif (seg == []): - seg = intersections[intersection][0][SEG] - vert = intersections[intersection][0][SEG] - oldseg = [] - else: - oldseg = seg - intersection = [] - while (intersection == []): - if (vert < (len(path[seg])-1)): - vert += 1 - else: - vert = 0 - intersection = path[seg][vert][INTERSECT] - if (intersection == -1): - intersection = istart - break - elif (intersection == istart): - #print ' back to',istart - interior = FALSE - intersection = istart - break - # - # save path if valid boundary intersection - # - if (interior == FALSE): - newseg = len(newpath) - newpath.append([]) - while 1: - #print 'keeping intersection',intersection,':',intersections[intersection] - [seg,vert] = union(intersection,path,intersections,sign) - if (seg == []): - seg = intersections[intersection][0][SEG] - vert = intersections[intersection][0][VERT] - #print ' seg',seg,'vert',vert - intersections[intersection] = [] - intersection = [] - while (intersection == []): - if (vert < (len(path[seg])-1)): - x = path[seg][vert][X] - y = path[seg][vert][Y] - newpath[newseg].append([x,y,[]]) - vert += 1 - else: - vert = 0 - intersection = path[seg][vert][INTERSECT] - if (intersection == istart): - #print ' back to',istart - x = path[seg][vert][X] - y = path[seg][vert][Y] - newpath[newseg].append([x,y,[]]) - break - i += 1 - return newpath - -def add_intersections(path): - ''' - Uses intersect() and insert() (FIX THIS, BELONG TO OTHER LIBRARY) - ''' - # - # add vertices at path intersections - # - events = [] - active = [] - # - # lexicographic sort segments - # - for seg in range(len(path)): - nverts = len(path[seg]) - for vert in range(nverts-1): - x0 = path[seg][vert][X] - y0 = path[seg][vert][Y] - x1 = path[seg][vert+1][X] - y1 = path[seg][vert+1][Y] - if (x1 < x0): - [x0, x1] = [x1, x0] - [y0, y1] = [y1, y0] - if ((x1 == x0) & (y1 < y0)): - [y0, y1] = [y1, y0] - events.append([x0,y0,START,seg,vert]) - events.append([x1,y1,END,seg,vert]) - events.sort() - # - # find intersections with a sweep line - # - intersection = 0 - verts = [] - for event in range(len(events)): -# status.set(" edge "+str(event)+"/"+str(len(events)-1)+" ") -# outframe.update() - # - # loop over start/end points - # - type = events[event][INDEX] - seg0 = events[event][EVENT_SEG] - vert0 = events[event][EVENT_VERT] - n0 = len(path[seg0]) - if (events[event][INDEX] == START): - # - # loop over active points - # - for point in range(len(active)): - sega = active[point][SEG] - verta = active[point][VERT] - if ((sega == seg0) & \ - ((abs(vert0-verta) == 1) | (abs(vert0-verta) == (n0-2)))): - #print seg0,vert0,verta,n0 - continue - [xloc,yloc] = intersect(path,seg0,vert0,sega,verta) - if (xloc != []): - # - # found intersection, save it - # - d0 = (path[seg0][vert0][X]-xloc)**2 + (path[seg0][vert0][Y]-yloc)**2 - verts.append([seg0,vert0,d0,xloc,yloc,intersection]) - da = (path[sega][verta][X]-xloc)**2 + (path[sega][verta][Y]-yloc)**2 - verts.append([sega,verta,da,xloc,yloc,intersection]) - intersection += 1 - active.append([seg0,vert0]) - else: - active.remove([seg0,vert0]) - print " found",intersection,"intersections" - # - # add vertices at path intersections - # - verts.sort() - verts.reverse() - for vertex in range(len(verts)): - seg = verts[vertex][SEG] - vert = verts[vertex][VERT] - intersection = verts[vertex][IINTERSECT] - x = verts[vertex][XINTERSECT] - y = verts[vertex][YINTERSECT] - insert(path,x,y,seg,vert,intersection) - # - # make vertex table and segment list of intersections - # -# status.set(namedate) -# outframe.update() - nintersections = len(verts)/2 - intersections = [[] for i in range(nintersections)] - for seg in range(len(path)): - for vert in range(len(path[seg])): - intersection = path[seg][vert][INTERSECT] - if (intersection != []): - intersections[intersection].append([seg,vert]) - seg_intersections = [[] for i in path] - for i in range(len(intersections)): - if (len(intersections[i]) != 2): - print " shouldn't happen: i",i,intersections[i] - else: - seg_intersections[intersections[i][0][SEG]].append(i) - seg_intersections[intersections[i][A][SEG]].append(i) - return [path, intersections, seg_intersections] - -def intersect(path,seg0,vert0,sega,verta): - # - # test and return edge intersection - # - if ((seg0 == sega) & (vert0 == 0) & (verta == (len(path[sega])-2))): - #print " return (0-end)" - return [[],[]] - x0 = path[seg0][vert0][X] - y0 = path[seg0][vert0][Y] - x1 = path[seg0][vert0+1][X] - y1 = path[seg0][vert0+1][Y] - dx01 = x1 - x0 - dy01 = y1 - y0 - d01 = sqrt(dx01*dx01 + dy01*dy01) - if (d01 == 0): - # - # zero-length segment, return no intersection - # - #print "zero-length segment" - return [[],[]] - dxpar01 = dx01 / d01 - dypar01 = dy01 / d01 - dxperp01 = dypar01 - dyperp01 = -dxpar01 - xa = path[sega][verta][X] - ya = path[sega][verta][Y] - xb = path[sega][verta+1][X] - yb = path[sega][verta+1][Y] - dx0a = xa - x0 - dy0a = ya - y0 - dpar0a = dx0a*dxpar01 + dy0a*dypar01 - dperp0a = dx0a*dxperp01 + dy0a*dyperp01 - dx0b = xb - x0 - dy0b = yb - y0 - dpar0b = dx0b*dxpar01 + dy0b*dypar01 - dperp0b = dx0b*dxperp01 + dy0b*dyperp01 - #if (dperp0a*dperp0b > EPS): - if (((dperp0a > EPS) & (dperp0b > EPS)) | \ - ((dperp0a < -EPS) & (dperp0b < -EPS))): - # - # vertices on same side, return no intersection - # - #print " same side" - return [[],[]] - elif ((abs(dperp0a) < EPS) & (abs(dperp0b) < EPS)): - # - # edges colinear, return no intersection - # - #d0a = (xa-x0)*dxpar01 + (ya-y0)*dypar01 - #d0b = (xb-x0)*dxpar01 + (yb-y0)*dypar01 - #print " colinear" - return [[],[]] - # - # calculation distance to intersection - # - d = (dpar0a*abs(dperp0b)+dpar0b*abs(dperp0a))/(abs(dperp0a)+abs(dperp0b)) - if ((d < -EPS) | (d > (d01+EPS))): - # - # intersection outside segment, return no intersection - # - #print " found intersection outside segment" - return [[],[]] - else: - # - # intersection in segment, return intersection - # - #print " found intersection in segment s0 v0 sa va",seg0,vert0,sega,verta - xloc = x0 + dxpar01*d - yloc = y0 + dypar01*d - return [xloc,yloc] - -def insert(path,x,y,seg,vert,intersection): - # - # insert a vertex at x,y in seg,vert, if needed - # - d0 = (path[seg][vert][X]-x)**2 + (path[seg][vert][Y]-y)**2 - d1 = (path[seg][vert+1][X]-x)**2 + (path[seg][vert+1][Y]-y)**2 - #print "check insert seg",seg,"vert",vert,"intersection",intersection - if ((d0 > EPS) & (d1 > EPS)): - #print " added intersection vertex",vert+1 - path[seg].insert((vert+1),[x,y,intersection]) - return 1 - elif (d0 < EPS): - if (path[seg][vert][INTERSECT] == []): - path[seg][vert][INTERSECT] = intersection - #print " added d0",vert - return 0 - elif (d1 < EPS): - if (path[seg][vert+1][INTERSECT] == []): - path[seg][vert+1][INTERSECT] = intersection - #print " added d1",vert+1 - return 0 - else: - #print " shouldn't happen: d0",d0,"d1",d1 - return 0 - -def union(i,path,intersections,sign): - # - # return edge to exit intersection i for a union - # - #print "union: intersection",i,"in",intersections - seg0 = intersections[i][0][SEG] - #print "seg0",seg0 - vert0 = intersections[i][0][VERT] - x0 = path[seg0][vert0][X] - y0 = path[seg0][vert0][Y] - if (vert0 < (len(path[seg0])-1)): - vert1 = vert0 + 1 - else: - vert1 = 0 - x1 = path[seg0][vert1][X] - y1 = path[seg0][vert1][Y] - dx01 = x1-x0 - dy01 = y1-y0 - sega = intersections[i][A][SEG] - verta = intersections[i][A][VERT] - xa = path[sega][verta][X] - ya = path[sega][verta][Y] - if (verta < (len(path[sega])-1)): - vertb = verta + 1 - else: - vertb = 0 - xb = path[sega][vertb][X] - yb = path[sega][vertb][Y] - dxab = xb-xa - dyab = yb-ya - dot = dxab*dy01 - dyab*dx01 - #print " dot",dot - if (abs(dot) <= EPS): - print " colinear" - seg = [] - vert= [] - elif (dot > EPS): - seg = intersections[i][(1-sign)/2][SEG] - vert = intersections[i][(1-sign)/2][VERT] - else: - seg = intersections[i][(1+sign)/2][SEG] - vert = intersections[i][(1+sign)/2][VERT] - return [seg,vert] - -# MODIFIED -def read(filename): #MOD - event = None #MOD - print "read(event)" - global vertices, faces, boundarys, toolpaths, contours, slices,\ - xmin, xmax, ymin, ymax, zmin, zmax, noise_flag - # - # read file - # - faces = [] - contours = [[]] - boundarys = [[]] - toolpaths = [[]] - slices = [[]] - #filename = infile.get() #MOD - if ((find(filename,".cmp") != -1) | (find(filename,".CMP")!= -1) \ - | (find(filename,".sol")!= -1) | (find(filename,".SOL") != -1) \ - | (find(filename,".plc")!= -1) | (find(filename,".PLC")!= -1) \ - | (find(filename,".sts")!= -1) | (find(filename,".STS")!= -1) \ - | (find(filename,".gtl")!= -1) | (find(filename,".GTL")!= -1) \ - | (find(filename,".stc")!= -1) | (find(filename,".STC")!= -1)): - print "reading Gerber file",filename - read_Gerber(filename) - elif ((find(filename,".drl") != -1) | (find(filename,".DRL") != -1) | \ - (find(filename,".drd") != -1) | (find(filename,".DRD") != -1)): - print "reading Excellon file",filename - read_Excellon(filename) - elif ((find(filename,".dxf") != -1) | (find(filename,".DXF") != -1)): - print "reading DXF file",filename - read_DXF(filename) - elif (find(filename,".stl") != -1): - print "reading STL file",filename - read_STL(filename) - elif (find(filename,".jpg") != -1): - print "reading image file",filename - read_image(filename) - elif (find(filename,".svg") != -1): - print "reading SVG file",filename - read_SVG(filename) - else: - print "unsupported file type" - return - xmin = HUGE - xmax = -HUGE - ymin = HUGE - ymax = -HUGE - zmin = HUGE - zmax = -HUGE - if (len(boundarys) == 1): - # - # 2D file - # - boundary = boundarys[0] - sum = 0 - for segment in range(len(boundary)): - sum += len(boundary[segment]) - for vertex in range(len(boundary[segment])): - x = boundary[segment][vertex][X] - y = boundary[segment][vertex][Y] - if (x < xmin): xmin = x - if (x > xmax): xmax = x - if (y < ymin): ymin = y - if (y > ymax): ymax = y - print " found",len(boundary),"polygons,",sum,"vertices" - print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"dx: %0.3g "%(xmax-xmin) - print " ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax,"dy: %0.3g "%(ymax-ymin) - if (noise_flag == 1): - if ((xmax-xmin) < (ymax-ymin)): - delta = (xmax-xmin)*NOISE - else: - delta = (ymax-ymin)*NOISE - for segment in range(len(boundary)): - for vertex in range(len(boundary[segment])): - boundary[segment][vertex][X] += gauss(0,delta) - boundary[segment][vertex][Y] += gauss(0,delta) - print " added %.3g perturbation"%delta - boundarys[0] = boundary - elif (len(boundarys) > 1): - # - # 3D layers - # - for layer in range(len(boundarys)): - boundary = boundarys[layer] - sum = 0 - for segment in range(len(boundary)): - sum += len(boundary[segment]) - for vertex in range(len(boundary[segment])): - x = boundary[segment][vertex][X3] - y = boundary[segment][vertex][Y3] - z = boundary[segment][vertex][Z3] - if (x < xmin): xmin = x - if (x > xmax): xmax = x - if (y < ymin): ymin = y - if (y > ymax): ymax = y - if (z < zmin): zmin = z - if (z > zmax): zmax = z - print " layer",layer,"found",len(boundary),"polygon(s),",sum,"vertices" - if (noise_flag == 1): - if ((xmax-xmin) < (ymax-ymin)): - delta = (xmax-xmin)*NOISE - else: - delta = (ymax-ymin)*NOISE - for segment in range(len(boundary)): - for vertex in range(len(boundary[segment])): - boundary[segment][vertex][X3] += gauss(0,delta) - boundary[segment][vertex][Y3] += gauss(0,delta) - boundary[segment][vertex][Z3] += gauss(0,delta) - boundarys[layer] = boundary - print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"dx: %0.3g "%(xmax-xmin) - print " ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax,"dy: %0.3g "%(ymax-ymin) - print " zmin: %0.3g "%zmin,"zmax: %0.3g "%zmax,"dy: %0.3g "%(zmax-zmin) - print " added %.3g perturbation"%delta - elif (faces != []): - # - # 3D faces - # - for vertex in range(len(vertices)): - x = vertices[vertex][X] - y = vertices[vertex][Y] - z = vertices[vertex][Z] - if (x < xmin): xmin = x - if (x > xmax): xmax = x - if (y < ymin): ymin = y - if (y > ymax): ymax = y - if (z < zmin): zmin = z - if (z > zmax): zmax = z - print " found",len(vertices),"vertices,",len(faces),"faces" - print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"dx: %0.3g "%(xmax-xmin) - print " ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax,"dy: %0.3g "%(ymax-ymin) - print " zmin: %0.3g "%zmin,"zmax: %0.3g "%zmax,"dz: %0.3g "%(zmax-zmin) - if (noise_flag == 1): - delta = (zmax-zmin)*NOISE - for vertex in range(len(vertices)): - vertices[vertex][X] += gauss(0,delta) - vertices[vertex][Y] += gauss(0,delta) - vertices[vertex][Z] += gauss(0,delta) - print " added %.3g perturbation"%delta - else: - print "shouldn't happen in read" - #camselect(event) MOD - print "End read(event)" - -def write_G(boundarys, toolpaths, scale=1.0, thickness=1.0, feed=1, zclear=0.1, zcut=-0.005): - X = 0 - Y = 1 - #global boundarys, toolpaths, xmin, ymin, zmin, zmax - # - # G code output - # - #xyscale = float(sxyscale.get()) - xyscale = scale - #zscale = float(sxyscale.get()) - #zscale = scale - #dlayer = float(sthickness.get())/zscale - #dlayer = thickness/zscale - #feed = float(sfeed.get()) - #xoff = float(sxmin.get()) - xmin*xyscale - #yoff = float(symin.get()) - ymin*xyscale - #cool = icool.get() - #text = outfile.get() - - output = "" #file = open(text, 'w') - output += "%\n" #file.write("%\n") - output += "O1234\n" #file.write("O1234\n") - #file.write("T"+stool.get()+"M06\n") # tool - output += "G90G54\n" #file.write("G90G54\n") # absolute positioning with respect to set origin - output += "F%0.3f\n"%feed #file.write("F%0.3f\n"%feed) # feed rate - #file.write("S"+sspindle.get()+"\n") # spindle speed - #if (cool == TRUE): file.write("M08\n") # coolant on - output += "G00Z%.4f\n"%zclear #file.write("G00Z"+szup.get()+"\n") # move up before starting spindle - output += "M03\n" #file.write("M03\n") # spindle on clockwise - nsegment = 0 - for layer in range((len(boundarys)-1),-1,-1): - if (toolpaths[layer] == []): - path = boundarys[layer] - else: - path = toolpaths[layer] - #if (szdown.get() == " "): - # zdown = zoff + zmin + (layer-0.50)*dlayer - #else: - # zdown = float(szdown.get()) - for segment in range(len(path)): - nsegment += 1 - vertex = 0 - x = path[segment][vertex][X]*xyscale #+ xoff - y = path[segment][vertex][Y]*xyscale #+ yoff - output += "G00X%0.4f"%x+"Y%0.4f"%y+"Z%.4f"%zclear+"\n" #file.write("G00X%0.4f"%x+"Y%0.4f"%y+"Z"+szup.get()+"\n") # rapid motion - output += "G01Z%0.4f"%zcut+"\n" #file.write("G01Z%0.4f"%zdown+"\n") # linear motion - for vertex in range(1,len(path[segment])): - x = path[segment][vertex][X]*xyscale #+ xoff - y = path[segment][vertex][Y]*xyscale #+ yoff - output += "X%0.4f"%x+"Y%0.4f"%y+"\n" #file.write("X%0.4f"%x+"Y%0.4f"%y+"\n") - output += "Z%.4f\n"%zclear #file.write("Z"+szup.get()+"\n") - output += "G00Z%.4f\n"%zclear #file.write("G00Z"+szup.get()+"\n") # move up before stopping spindle - output += "M05\n" #file.write("M05\n") # spindle stop - #if (cool == TRUE): file.write("M09\n") # coolant off - output += "M30\n" #file.write("M30\n") # program end and reset - output += "%\n" #file.write("%\n") - #file.close() - print "wrote",nsegment,"G code toolpath segments" - return output ################ end of cam.py ############# diff --git a/camlib.pyc b/camlib.pyc index 0697d73bd13cbac29d7d316af90a5f1478179ba7..04f9034ea030fa485a2b7e1a5bbbd42af0d3ab8f 100644 GIT binary patch delta 2928 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 Geometry" + 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()) - gerber.create_geometry() 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() - f = Figure(dpi=75) - a = f.add_subplot(111) - a.set_aspect(1) - for poly in gerber.solid_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) + #a.plot(x, y) + self.axes.plot(x, y, linespec) for ints in poly.interiors: x, y = ints.coords.xy - a.plot(x, y) - a.grid() - 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() + 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): - self.positionLabel.set_label("X: %.4f Y: %.4f"%(event.xdata, event.ydata)) + 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() + width = xmax-xmin + height = ymax-ymin + + if center == None: + center = [(xmin+xmax)/2.0, (ymin+ymax)/2.0] + + # For keeping the point at the pointer location + relx = (xmax-center[0])/width + rely = (ymax-center[1])/height + + new_width = width/factor + new_height = height/factor + + self.axes.set_xlim((center[0]-new_width*(1-relx), center[0]+new_width*relx)) + self.axes.set_ylim((center[1]-new_height*(1-rely), center[1]+new_height*rely)) + + self.canvas.queue_draw() + +# 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 + + def on_window_key_press(self, widget, event): + print event.get_keycode(), event.get_keyval() + val = int(event.get_keyval()[1]) + + if val == 49: # 1 + self.on_zoom_fit(None) + return + + if val == 50: # 2 + self.zoom(1/1.5, self.mouse) + return + + if val == 51: # 3 + self.zoom(1.5, self.mouse) + return app = App() diff --git a/cirkuix.ui b/cirkuix.ui index 3cea5bb8..2bc82838 100644 --- a/cirkuix.ui +++ b/cirkuix.ui @@ -1,6 +1,11 @@ + + True + False + gtk-open + True False @@ -11,6 +16,7 @@ 400 False + True @@ -41,21 +47,22 @@ - Open geometry + Open Gerber True False image2 False - + - gtk-save + Open Excellon True False - True - True + image1 + False + @@ -175,6 +182,63 @@ 0 + + + True + False + icons + + + True + False + Zoom Fit + Fit + True + gtk-zoom-100 + + + + False + True + + + + + True + False + Zoom+ + Zoom+ + True + gtk-zoom-in + + + + False + True + + + + + True + False + Zoom- + Zoom- + True + gtk-zoom-out + + + + False + True + + + + + False + True + 1 + + True @@ -196,12 +260,13 @@ 3 vertical - - Merge Geometry + + Merge Polygons True True False 0 + True True @@ -211,8 +276,8 @@ - - Different Colors + + Multi-colored True True False @@ -226,42 +291,7 @@ - - True - False - - - True - False - Scale (pix./unit): - - - False - True - 0 - - - - - True - True - True - - 10 - True - - - False - True - 1 - - - - - False - True - 2 - + @@ -269,7 +299,7 @@ True False - Gerber + Options False @@ -294,50 +324,45 @@ - + True - True - in + False - + + + + + + + + 25 True False True - True - - - True - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + True + + 0 + 1 + 1 + 1 + + + + + 25 + True + False + True + vertical + True + + + 1 + 0 + 1 + 1 + @@ -349,7 +374,7 @@ False True - 1 + 2 @@ -404,7 +429,7 @@ False True - 2 + 3